./PaxHeaders/mdr0000644000000000000000000000013214672035463011010 xustar0030 mtime=1726495539.327401489 30 atime=1726495547.035593727 30 ctime=1726495539.327401489 mdr/0000755000175000001440000000000014672035463012561 5ustar00mateuszusers00000000000000mdr/PaxHeaders/rs232.h0000644000000000000000000000013214672035463011754 xustar0030 mtime=1726495539.327401489 30 atime=1726495547.035593727 30 ctime=1726495539.327401489 mdr/rs232.h0000644000175000001440000000375614672035463013620 0ustar00mateuszusers00000000000000/* * Reading from and writing to an RS-232 port * * This file is part of Mateusz' DOS Routines * Published under the terms of the MIT License, as stated below. * * Copyright (C) 2014-2024 Mateusz Viste * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to * deal in the Software without restriction, including without limitation the * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or * sell copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS * IN THE SOFTWARE. */ #ifndef MDR_RS232_H #define MDR_RS232_H /* get the I/O port for COMx (1..4) */ unsigned short rs232_getport(int x); /* check if the COM port is ready for write. loops for some time waiting. * returns 0 if port seems ready eventually, non-zero otherwise. can be used * to verify the rs232 presence */ int rs232_check(unsigned short port); /* write a byte to the COM port at 'port'. this function will block if the * UART is not ready to transmit yet. */ void rs232_write(unsigned short port, int data); /* read a byte from COM port at 'port'. returns the read byte, or -1 if * nothing was available to read. */ int rs232_read(unsigned short port); #endif mdr/PaxHeaders/timer.h0000644000000000000000000000013214672035463012221 xustar0030 mtime=1726495539.327401489 30 atime=1726495547.035593727 30 ctime=1726495539.327401489 mdr/timer.h0000644000175000001440000000454514672035463014062 0ustar00mateuszusers00000000000000/* * High-resolution timing routines (PIT reprogramming) * * This file is part of Mateusz' DOS Routines * Published under the terms of the MIT License, as stated below. * * Copyright (C) 2014-2024 Mateusz Viste * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to * deal in the Software without restriction, including without limitation the * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or * sell copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS * IN THE SOFTWARE. */ #ifndef MDR_TIMER_H #define MDR_TIMER_H /* Starts the timer by reprogramming the 8253 chip from its default 18.2 Hz * frequency to about 1.1 kHz. It is mandatory to revert the timer to its * original frequency via mdr_timer_stop() before your application quits. */ void mdr_timer_init(void); /* Fills res with the amount of microseconds that elapsed since either * mdr_timer_init() or mdr_timer_reset(), whichever was called last. * Note that the res counter wraps around approximately every 71 minutes if * mdr_timer_reset() is not called. */ void mdr_timer_read(unsigned long *res); /* Reset the timer value, this can be used by the application to make sure * no timer wrap occurs during critical parts of your code flow */ void mdr_timer_reset(void); /* Stops (uninstalls) the timer. This must be called before your application * quits, otherwise the system will likely crash. This function has a void * return value so that it can be registered as an atexit() procedure. */ void mdr_timer_stop(void); #endif mdr/PaxHeaders/trigint.h0000644000000000000000000000013214672035463012561 xustar0030 mtime=1726495539.327401489 30 atime=1726495547.035593727 30 ctime=1726495539.327401489 mdr/trigint.h0000644000175000001440000000455014672035463014416 0ustar00mateuszusers00000000000000/****************************************************************************** * Routines for computation of basic transcendental functions sin and cos. * * These routines use only integers, hence they do not require an FPU nor any * * kind of FPU emulation. Works reasonably fast even on an 8086 CPU. * * * * The results are computed using polynomial approximations. Their precision * * is not expected to be ideal, but "good enough" for common usage. * ****************************************************************************** * * This file is part of Mateusz' DOS Routines * Published under the terms of the MIT License, as stated below. * * Copyright (C) 2014-2024 Mateusz Viste * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to * deal in the Software without restriction, including without limitation the * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or * sell copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS * IN THE SOFTWARE. */ #ifndef MDR_TRIGINT_H #define MDR_TRIGINT_H /* Computes the cosine value for the given radian. * The radian argument must be provided multiplied by 1000. * Returns the cosine value multiplied by 1000. */ short trigint_cos(short rad1000); /* Computes the sine value for the given radian angle. * The radian argument must be provided multiplied by 1000. * Returns the cosine value multiplied by 1000. */ short trigint_sin(short rad1000); #endif mdr/PaxHeaders/readme.txt0000644000000000000000000000013214672035463012726 xustar0030 mtime=1726495539.327401489 30 atime=1726495547.035593727 30 ctime=1726495539.327401489 mdr/readme.txt0000644000175000001440000000551314672035463014563 0ustar00mateuszusers00000000000000 Mateusz' DOS Routines http://mdrlib.sourceforge.io Mateusz' DOS Routines (MDR) is a C library that contains a variety of routines to ease the development of real mode DOS applications. These routines are mostly targeted at the Open Watcom compiler, but might work with other C compilers as well. All the routines have been created by Mateusz Viste and are published under the terms of the MIT license. List of available modules: BIOS BIOS-based functions COUT console output (writing to text-mode display) DOS functions interacting with DOS KEYB basic functions to interact with the keyboard MOUSE mouse routines OPL OPL2 (Adlib style) audio PCX parsing, loading and uncompressing PCX images PKTDRV packet driver interface (sending and receiving raw Ethernet frames) RS232 writing to and reading from an RS-232 ("COM") port SBDIGI playing digitized sounds with a SoundBlaster-compatible card TIMER high-resolution (1 kHz) timer, relies on PIT reprogramming TRIGINT sin and cos functions using integers only (8086-compatible) UNZIP iteration over ZIP archives (no decompression code) VID12 driver for mode 12h VGA graphic (640x480, 16 colors) VIDEO drivers for 320x200 video modes (256 colors on VGA, 16 colors on CGA) WAVE parsing and loading WAVE sound files XMS detecting and using XMS memory to store data Documentation is contained in header (*.H) files in the INC\MDR\ directory. +============================================================================+ | USAGE | +============================================================================+ Using MDR is no different than using any other library: you need to include the header file(s) you wish to rely on and pass the lib file that matches your memory model (small/compact/medium/large) to your linker. Example program, KBTEST.C: #include int main(void) { mdr_dos_getkey(); return(0); } How to compile with the Watcom C Compile and Link Utility: wcl -ms kbtest.c mdrs2024.lib +============================================================================+ | COMPILATION FROM SOURCES | +============================================================================+ Should you wish to compile MDR from sources instead of relying on precompiled LIB binaries, you will need the Watcom (or Open Watcom) C compiler and use its wmake utility as follows: wmake clean wmake model= valid memory model options are: wmake model=s wmake model=c wmake model=m wmake model=l ============================================================================== mdr/PaxHeaders/vid12.h0000644000000000000000000000013214672035463012026 xustar0030 mtime=1726495539.327401489 30 atime=1726495547.035593727 30 ctime=1726495539.327401489 mdr/vid12.h0000644000175000001440000000721114672035463013660 0ustar00mateuszusers00000000000000/* * a few functions for mode 12h programming (640x480 4bpp) * * This file is part of Mateusz' DOS Routines * Published under the terms of the MIT License, as stated below. * * Copyright (C) 2014-2024 Mateusz Viste * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to * deal in the Software without restriction, including without limitation the * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or * sell copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS * IN THE SOFTWARE. */ #ifndef MDR_VID12_H #define MDR_VID12_H /* init video mode 12h (640x480x16c) * remember to call vid12_close() at exit time */ void vid12_init(void); /* wait until VBLANK */ void vid12_waitvblank(void); /* wait until ANY blank: either VBLANK or HBLANK */ void vid12_waitblank(void); /* clear screen using color * this function is fastest when color is 0 or 15 */ void vid12_cls(unsigned char color); /* clear a single scanline (0..479) with a solid color (0..15) * this function is fastest when color is 0 or 15 */ void vid12_clrline(unsigned short line, unsigned char color); /* fill lines from linefirst to linelast with an 8 pixels pattern * linelast must be equal to or greater than linelast * pattern must be 8 bytes long */ void vid12_linepat(unsigned short linefirst, unsigned short linelast, const unsigned char *pattern); /* deinit video mode 12h and resets to previous mode */ void vid12_close(void); /* puts a pixel on screen */ void vid12_putpixel(unsigned short x, unsigned short y, unsigned char col); /* draws a horizonatal line from [x1,y] to [x2,y] */ void vid12_hline(unsigned short y, unsigned short x1, unsigned short x2, unsigned char color); /* write an entire scanline (640 pixels) to screen. the pixels data must be * a serie of 640 bytes having values in the range [0..15] */ void vid12_putscanline(unsigned short scanline, const unsigned char *pixels); /* set index palette color to given R,G,B value. each R,G,B component must be * a 6 bit value in the range [0..63] (where 63 is the maximum luminosity) */ void vid12_setpalette(unsigned char index, unsigned char r, unsigned char g, unsigned char b); /***************************** * VRAM TO VRAM operations * *****************************/ /* prepares VGA for a VRAM-to-VRAM copy operation */ void vid12_vramcpy_prep(void); /* fast (VRAM-to-VRAM) copy of a scanline (0..479) to another scanline. * vid12_vramcpy_prep() must be called before and vid12_vramcpy_done must be * called after one or more vid12_vramcpy_scanline() calls. */ void vid12_vramcpy_scanline(unsigned short dst, unsigned short src); /* sets VGA back to its normal state after VRAM-to-VRAM operations */ void vid12_vramcpy_done(void); #endif mdr/PaxHeaders/dos.h0000644000000000000000000000013214672035463011666 xustar0030 mtime=1726495539.327401489 30 atime=1726495547.035593727 30 ctime=1726495539.327401489 mdr/dos.h0000644000175000001440000001167014672035463013524 0ustar00mateuszusers00000000000000/* * Functions interacting with DOS * * This file is part of Mateusz' DOS Routines * Published under the terms of the MIT License, as stated below. * * Copyright (C) 2014-2024 Mateusz Viste * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to * deal in the Software without restriction, including without limitation the * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or * sell copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS * IN THE SOFTWARE. */ #ifndef MDR_DOS_H #define MDR_DOS_H #include /* time_t */ /* returns a far pointer to the current process PSP structure */ void far *mdr_dos_psp(void); /* returns a far pointer to the environment block of the current process */ char far *mdr_dos_env(void); /* looks for varname in the DOS environment and fills result with its value if * found. returns NULL if not found or if value is too big to fit in result * (reslimit exceeded). returns result on success. * NOTE: this function performs case-sensitive matches */ char *mdr_dos_getenv(char *result, const char *varname, unsigned short reslimit); /* fetches directory where the program was loaded from and return its length. * path string is never longer than 128 (incl. the null terminator) and it is * always terminated with a backslash separator, unless it is an empty string */ unsigned char mdr_dos_exepath(char *path); /* returns a far pointer to the full path and filename of the running program. * returns NULL on error. */ const char far *mdr_dos_selfexe(void); /* waits for a keypress and returns it * extended keys are returned ORed with 0x100 (example: PGUP is 0x149) */ int mdr_dos_getkey(void); /* Same as mdr_dos_getkey(), but this call cannot be aborted by CTRL+C */ int mdr_dos_getkey2(void); /* flush the keyboard buffer */ void mdr_dos_flushkeyb(void); /* poll stdin status, returns 0 if no character is pending in the keyboard * buffer, non-zero otherwise */ int mdr_dos_keypending(void); /* sets up the CTRL+C handler for the running program to a no-op - in other * words, after this call DOS will no longer abort the program on CTRL+C. * this is only valid for the duration of the program because DOS will restore * the original handler after the program exits. * * an alternative is mdr_dos_ctrlc_off(), but this does not inhibit the * handler, it sets DOS to not react to CTRL+C in the first place, and this * setting stays active after the program quits so the program should remember * to restore the original setting before quitting. */ void mdr_dos_ctrlc_inhibit(void); /* sets the DOS BREAK control OFF, ie. instructs DOS not to check for CTRL+C * during most input operations. returns the previous state of the break * control flag (0=disabled 1=enabled). this changes a global DOS flag that can * be checked on command line with the "BREAK" command, so the program should * take care to restore the initial setting before quitting. */ unsigned char mdr_dos_ctrlc_disable(void); /* sets the DOS BREAK control ON. see mdr_dos_ctrlc_disable() for details. */ void mdr_dos_ctrlc_enable(void); /* converts a "DOS format" 16-bit packed date into a standard (time_t) * unix timestamp. A DOS date is a 16-bit value: * YYYYYYYM MMMDDDDD * * day of month is always within 1-31 range; * month is always within 1-12 range; * year starts at 1980 and continues for 127 years */ time_t mdr_dos_date2unix(unsigned short d); /* converts a "DOS format" 16-bit packed time into hours, minutes and seconds * * A DOS time is a 16-bit value: * HHHHHMMM MMMSSSSS * * HHHHH = hours, always within 0-23 range * MMMMMM = minutes, always within 0-59 range * SSSSS = seconds/2 (always within 0-29 range) */ void mdr_dos_time2hms(unsigned char *h, unsigned char *m, unsigned char *s, unsigned short t); /* Determine the canonical name of the specified filename or path and writes * the result into result. The input path does not need to actually exist. * This function requires a 3.x+ DOS kernel. * name is simply copied to result on error. */ void mdr_dos_truename(char *result, const char *name); #endif mdr/PaxHeaders/mouse.h0000644000000000000000000000013214672035463012231 xustar0030 mtime=1726495539.327401489 30 atime=1726495547.035593727 30 ctime=1726495539.327401489 mdr/mouse.h0000644000175000001440000000546114672035463014070 0ustar00mateuszusers00000000000000/* * Mouse routines * * This file is part of Mateusz' DOS Routines * Published under the terms of the MIT License, as stated below. * * Copyright (C) 2014-2024 Mateusz Viste * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to * deal in the Software without restriction, including without limitation the * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or * sell copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS * IN THE SOFTWARE. */ #ifndef MDR_MOUSE_H #define MDR_MOUSE_H /* init the mouse driver (and checks for presence of mouse support at same time) * returns 0 if no mouse is present, and the number of buttons otherwise. * after initialization the mouse cursor is hidden, use mdr_mouse_show() to * make it visible. */ int mdr_mouse_init(void); /* shows the mouse pointer */ void mdr_mouse_show(void); /* hides the mouse pointer */ void mdr_mouse_hide(void); /* get x/y coordinates of the mouse, and returns a bitfield with state of buttons */ int mdr_mouse_getstate(int *x, int *y); /* get x/y coordinates of the mouse when the last button release occured since last check. returns the id of the button pressed (1 or 2), or 0 if no event occured. */ int mdr_mouse_fetchrelease(int *x, int *y); /* set graphic pointer shape. icon is 64-bytes long, two sets of 32. each set * of 32 bytes is organized as a 16x16 bitmap, ie. 16 rows of 16-bit values. * a) the first set of 16 shorts defines the background mask - that is, the * background will show through wherever there is a 1-bit in that data. * b) the second set defines the "XOR mask" - that is, the pixels matching the * 1-bit in this data set are toggled. * hotspotx and hotspoty define respectively the horizontal and vertical hot * spot of the pointer (default being [0,0], that is the top left corner). */ void mdr_mouse_setcursor(const unsigned short *icon, unsigned char hotspotx, unsigned char hotspoty); #endif mdr/PaxHeaders/opl.h0000644000000000000000000000013214672035463011673 xustar0030 mtime=1726495539.327401489 30 atime=1726495547.035593727 30 ctime=1726495539.327401489 mdr/opl.h0000644000175000001440000002634714672035463013540 0ustar00mateuszusers00000000000000/* * Library to access OPL2/OPL3 hardware (YM3812 / YMF262) * * This file is part of Mateusz' DOS Routines * Published under the terms of the MIT License, as stated below. * * Copyright (C) 2014-2024 Mateusz Viste * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to * deal in the Software without restriction, including without limitation the * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or * sell copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS * IN THE SOFTWARE. */ #ifndef mdr_opl_h #define mdr_opl_h struct mdr_opl_timbre { unsigned char mod_ws, car_ws; /* waveform select (0-4), reg Exh */ unsigned char mod_sr, car_sr; /* sustain/release, reg 8xh */ unsigned char mod_ad, car_ad; /* attack/decay, reg 6xh */ unsigned char mod_20, car_20; /* tremolo/vibrato/sustain..., reg 2xh */ unsigned char mod_40, car_40; /* reg 4xh */ unsigned char feedconn; }; struct mdr_opl_timbretemplate { struct { unsigned char ws; /* waveform select 0..3 */ unsigned char sustlev; /* sustain level 0..15 */ unsigned char release; /* release level 0..15 */ unsigned char attack; /* attack rate 0..15 */ unsigned char decay; /* decay rate 0..15 */ unsigned char tremolo; /* tremolo flag 0..1 */ unsigned char vibrato; /* vibrato flag 0..1 */ unsigned char sustain; /* sustain flag 0..1 */ unsigned char ksr; /* KSR (envelope scaling) flag 0..1 */ unsigned char mult; /* frequency multiplication factor 0..15 */ unsigned char ksl; /* Key Scale Level 0..3 */ unsigned char outlev; /* output level 0..63 */ } carrier; struct { unsigned char ws; /* waveform select 0..3 */ unsigned char sustlev; /* sustain level 0..15 */ unsigned char release; /* release level 0..15 */ unsigned char attack; /* attack rate 0..15 */ unsigned char decay; /* decay rate 0..15 */ unsigned char tremolo; /* tremolo flag 0..1 */ unsigned char vibrato; /* vibrato flag 0..1 */ unsigned char sustain; /* sustain flag 0..1 */ unsigned char ksr; /* KSR (envelope scaling) flag 0..1 */ unsigned char mult; /* frequency multiplication factor 0..15 */ unsigned char ksl; /* Key Scale Level 0..3 */ unsigned char outlev; /* output level 0..63 */ } modultr; unsigned char feedback;/* FeedBack Modulation Factor 0..7 */ unsigned char conn; /* Synthesis type: 0=FM / 1=Additive */ }; enum MDR_OPL_TIMER { MDR_OPL_TIMER_80US = 2, MDR_OPL_TIMER_320US = 3 }; /* frequency groups, to be used with mdr_opl_noteon() and mdr_opl_notebend(). * There are 7 frequency groups to choose from. Each group supports a different * span of frequencies. Higher groups have wider spans, but at the cost of larger * difference between adjacent notes: * * Block Note 0 Note 1023 Step gap between adjacent notes * FGROUP0 0.047 Hz 48.503 Hz 0.048 Hz * FGROUP1 0.094 Hz 97.006 Hz 0.095 Hz * FGROUP2 0.189 Hz 194.013 Hz 0.190 Hz * FGROUP3 0.379 Hz 388.026 Hz 0.379 Hz * FGROUP4 0.758 Hz 776.053 Hz 0.759 Hz * FGROUP5 1.517 Hz 1552.107 Hz 1.517 Hz * FGROUP6 3.034 Hz 3104.215 Hz 3.034 Hz * FGROUP7 6.068 Hz 6208.431 Hz 6.069 Hz * * This shows that block 7 is capable of reaching the highest note (6.2kHz) but * since there are 6 Hz between notes the accuracy suffers. Example: note A-4 * is 440Hz but in this block, the two closest frequency numbers are 72 and 73, * which create tones at 437Hz and 443Hz respectively, neither of which is * particularly accurate. Blocks 3 and below are unable to reach as high as * 440Hz, but block 4 can. With block 4, frequency numbers 579 and 580 produce * 439.4 Hz and 440.2 Hz, considerably closer to the intended frequency. * * In other words, when calculating notes, the best accuracy is achieved by * selecting the lowest possible block number that can reach the desired note * frequency. * * More details: https://moddingwiki.shikadi.net/wiki/OPL_chip#A0-A8:_Frequency_Number */ enum mdr_opl_fgroup_t { MDR_OPL_FGROUP0 = 0, MDR_OPL_FGROUP1 = 1 << 2, MDR_OPL_FGROUP2 = 2 << 2, MDR_OPL_FGROUP3 = 3 << 2, MDR_OPL_FGROUP4 = 4 << 2, MDR_OPL_FGROUP5 = 5 << 2, MDR_OPL_FGROUP6 = 6 << 2, MDR_OPL_FGROUP7 = 7 << 2 }; /* Hardware detection and initialization. Must be called before any other * OPL function. Returns 0 on success, non-zero otherwise. */ int mdr_opl_init(void); /* close OPL device */ void mdr_opl_close(void); /* turns off all notes */ void mdr_opl_clear(void); /* loads an instrument described by properties in a timbre_t struct into * the defined voice channel. The OPL2 chip supports up to 9 voice channels, * from 0 to 8. The timbre struct can be freed right after this call. */ void mdr_opl_loadinstrument(unsigned char voice, const struct mdr_opl_timbre *timbre); /* generate a timbre structure based on a timbre template. this is a * convenience function meant to provide a human-compatible (more readable) * way of generating a timbre struct. */ int mdr_opl_timbre_gen(struct mdr_opl_timbre *timbre, const struct mdr_opl_timbretemplate *tpl); /* Triggers a note on selected voice channel. * freqid is a value between 0 and 1023. The following formula can be used to * determine the freq number for a given note frequency (Hz) and block: * * freqid = frequency * 2^(20 - block) / 49716 * * The note will be kept "pressed" until mdr_opl_noteoff() is called. */ void mdr_opl_noteon(unsigned char voice, unsigned short freqid, enum mdr_opl_fgroup_t fgroup); /* changes the frequency of the note currently playing on voice channel, this * can be used for pitch bending. */ void mdr_opl_notebend(unsigned char voice, unsigned short freqid, enum mdr_opl_fgroup_t fgroup); /* releases a note on selected voice. */ void mdr_opl_noteoff(unsigned char voice); /* adjusts volume of a voice. volume goes from 63 (mute) to 0 (loudest) */ void mdr_opl_voicevolume(unsigned char voice, unsigned char volume); /* this is a LOW-LEVEL function that writes a data byte into the reg register * of the OPL chip. Use this only if you know exactly what you are doing. */ void mdr_opl_regwr(unsigned char reg, unsigned char data); /***************************************************************************** * IMF AUDIO FILES PLAYBACK * * * * It is possible to mix IMF playback calls with manual notes, but you must * * take care to use only voices not used by your IMF audio. Typically games * * tend to use the voice #0 for sound effects and voices #1 to #8 for music. * * * * The IMF API comes in two version: the normal one, or "imfeasy". The easy * * version is easier to use, but requires to have the entire IMF audio file * * loaded in memory, while the normal (non-easy) allows for more flexibility * * in this regard, potentially allowing for playback of huge IMF files. * *****************************************************************************/ /*** EASY INTERFACE ***/ /* playback initialization, easy interface. imf points to the start of the IMF * file. The imf pointer must not be freed as long as playback is ongoing. * imflength is the size (in bytes) of the IMF data. * clock must be an incrementing value that wraps to 0 after 65535. The clock * speed will control the playback's tempo. * loopscount tells how many times the song will have to be looped (0 means * "loop forever"). * returns 0 on success, non-zero otherwise. */ int mdr_opl_imfeasy_init(void *imf, unsigned short imflength, unsigned short clock, unsigned char loopscount); /* Playback of an IMF file preloaded via mdr_opl_imfeasy_init(). This function * must be called repeatedly at a high frequency for best playback quality. * Returns 0 on success, 1 if playback ended, -1 on error. */ int mdr_opl_imfeasy_play(unsigned short clock); /*** ADVANCED INTERFACE ***/ /* playback initialization, this function must be called immediately before * playback. imf points to the start of the IMF file and must contain at least * the first 6 bytes of the audio file. * clock must be an incrementing value that wraps to 0 after 65535. * the clock speed will control the playback's tempo. * returns the amount of consumed bytes (0, 4 or 6) */ unsigned short mdr_opl_imf_init(void *imf, unsigned short clock); /* Playback, advanced version. Feeds the IMF playback routine with IMF data. * Returns the amount of bytes that have been consumed, hence the next call * should provide an imf pointer advanced by this many bytes (and imflen * decreased accordingly). Such approach might not be the most intuitive, but * it allows to load an imf song partially and provide only short chunks of * data for playback instead of having to buffer the entire song in memory. * For a simpler call that requires to buffer the entire IMF file in memory, * see mdr_opl_imf_playeasy(). * This function must be called repeatedly at a high frequency for best * playback quality. */ unsigned short mdr_opl_imf_play(void *imf, unsigned short imflen, unsigned short clock); /***************************************************************************** * OPL TIMER FUNCTIONS * *****************************************************************************/ /* configures and starts a timer given type so it emits a tick every count * periods. Two timer types are available: * MDR_OPL_TIMER_80US - with a period of 80us * MDR_OPL_TIMER_320US - with a period of 320us * count may range from 0 to 255, but 0 means "256 periods". * * You may use only one timer at a time. * * EXAMPLE: setting up MDR_OPL_TIMER_80US with a count of 25 would make the * timer tick every 2ms (25 * 80us). */ void mdr_opl_timer_set(enum MDR_OPL_TIMER timertype, unsigned char count); /* returns 1 if timer tick occured, 0 otherwise. After a tick has been * returned, this function will return 0 until next tick. * * it is important to note that there is no way to know whether one tick * passed since last time, or more, so it is up to you to make sure you call * this function fast enough. */ unsigned char mdr_opl_timer_tick(void); #endif mdr/PaxHeaders/pcx.h0000644000000000000000000000013214672035463011673 xustar0030 mtime=1726495539.327401489 30 atime=1726495547.035593727 30 ctime=1726495539.327401489 mdr/pcx.h0000644000175000001440000000664514672035463013537 0ustar00mateuszusers00000000000000/* * PCX-loading routines * * This file is part of Mateusz' DOS Routines * Published under the terms of the MIT License, as stated below. * * Copyright (C) 2014-2024 Mateusz Viste * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to * deal in the Software without restriction, including without limitation the * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or * sell copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS * IN THE SOFTWARE. */ #ifndef MDR_PCX_H #define MDR_PCX_H #include /* FILE */ struct pcx_hdr { unsigned char rle; unsigned char bpp; unsigned short max_x; unsigned short max_y; unsigned short bytes_per_scanline; struct { unsigned char r; unsigned char g; unsigned char b; } pal[256]; }; /* analyzes the header of a PCX file and fills the pcx_hdr struct accordingly. * fd must be a valid (open) file descriptor. * offset is the address inside the file where the PCX data is located * (usually 0, unless the file is some kind of container). * len is the total length of the PCX data. len=0 means "same as file size" * returns 0 on success, non-zero otherwise. */ int mdr_pcx_anal(struct pcx_hdr *h, FILE *fd, unsigned long offset, unsigned long len); /* this function should be called to load the next row of a PCX file into a * buffer pointed at by bufptr. you will typically want to call this function * h->max_y times. ptr must be at least (h->max_x + 1) bytes large for 8bpp. * the pcx_hdr struct must have been produced by pcx_anal(). * returns 0 on success, non-zero otherwise. */ int mdr_pcx_loadrow(void *bufptr, const struct pcx_hdr *h, FILE *fd); /* load an entire PCX file into a pixel buffer. the PCX data must have been * previously analyzed by pcx_anal() and the fd file pointer must not have been * modified since then. the destination buffer must be large enough to hold all * pixels, ie. (h->max_x + 1) * (h->max_y + 1) for 8 bpp. * returns 0 on success, non-zero otherwise. */ int mdr_pcx_load(void *ptr, const struct pcx_hdr *h, FILE *fd); /* convert img to 8bpp if needed (ie unpack 2 and 4bpp data to 8bpp). * the conversion is performed in-place, make sure the img buffer is large * enough to accomodate the size of the data after conversion (ie. twice as * big on 4bpp source, 4x times as big on 2bpp source and 8x as big on 1bpp * source). * if rowflag is set to a non-zero value, then the routine assumes img * contains only a single row of pixels */ int mdr_pcx_to8bpp(void *img, const struct pcx_hdr *h, unsigned char rowflag); #endif mdr/PaxHeaders/ver.h0000644000000000000000000000013214672035463011675 xustar0030 mtime=1726495539.327401489 30 atime=1726495547.035593727 30 ctime=1726495539.327401489 mdr/ver.h0000644000175000001440000000257014672035463013532 0ustar00mateuszusers00000000000000/* * MDR version * * This file is part of Mateusz' DOS Routines * Published under the terms of the MIT License, as stated below. * * Copyright (C) 2014-2024 Mateusz Viste * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to * deal in the Software without restriction, including without limitation the * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or * sell copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS * IN THE SOFTWARE. */ #ifndef MDR_VER_H #define MDR_VER_MAJOR 2025 #define MDR_VER_MINOR 0 #endif mdr/PaxHeaders/bios.h0000644000000000000000000000013214672035463012035 xustar0030 mtime=1726495539.327401489 30 atime=1726495547.035593727 30 ctime=1726495539.327401489 mdr/bios.h0000644000175000001440000000332014672035463013664 0ustar00mateuszusers00000000000000/* * BIOS functions * * This file is part of Mateusz' DOS Routines * Published under the terms of the MIT License, as stated below. * * Copyright (C) 2014-2024 Mateusz Viste * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to * deal in the Software without restriction, including without limitation the * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or * sell copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS * IN THE SOFTWARE. */ #ifndef MDR_BIOS_H #define MDR_BIOS_H /* waits for ticks time (1 tick is roughly 55ms, an hour has 65543 ticks) * works on IBM PC, XT, AT - ie. it's always safe */ void mdr_bios_tickswait(unsigned short ticks); /* returns the current BIOS tick counter (18.2 Hz, 1 tick is roughly 55ms, an * hour has 65543 ticks). works on IBM PC, XT, AT - ie. it's always safe */ unsigned short mdr_bios_ticks(void); #endif mdr/PaxHeaders/mdrs2025.lib0000644000000000000000000000013214672035463012676 xustar0030 mtime=1726495539.327401489 30 atime=1726495539.327401489 30 ctime=1726495539.327401489 mdr/mdrs2025.lib0000644000175000001440000014400014672035463014526 0ustar00mateuszusers00000000000000C:\SVN\MDR\bios\biostick.cT0sOed"[pWC:\SVN\MDR\bios\biostick.c!,YC:\SVN\MDR\inc\mdr\bios.h !CODEDATABSSTLSDGROUP_TEXT(" OCONSTטH CONST2H  _DATAH  R&SQRVWUPF2F]_^ZY[0mdr_bios_ticks_ _small_code_ math87sÈemu87T ZftC:\SVN\MDR\bios\tickwait.cT0sOed"[pWC:\SVN\MDR\bios\tickwait.c!,YC:\SVN\MDR\inc\mdr\bios.h !CODEDATABSSTLSDGROUP_TEXT(" OCONSTטH CONST2H  _DATAH  R&SQRVWUP2;u]_^ZY[ÿmdr_bios_tickswait_b _small_code_ math87sÈemu87T ZftC:\SVN\MDR\cout\cout.cT0sOedZ"YC:\SVN\MDR\cout\cout.c"雳YC:\SVN\MDR\inc\mdr\video.hˆ!ձ!YC:\SVN\MDR\inc\mdr\cout.h !CODEDATABSSTLSDGROUP_TEXT(OCONSTטH CONST2H  _DATAH _BSSH  LUPĈVΊFVيn]SQ Y[SQRVWUPF2F0]_^ZY[QVWUPRS0FF0FSQR2ZY[~tF^~tF^~tF^]_3mdr_cout_getcurattr_( _savedattr _term_height _term_widthQmdr_cout_getconprops_x& _cursor_shape _cursor_y _cursor_x%mdr_cursor_getinfo_ mdr_cout_cls_mdr_cout_scrollup_mdr_cout_cursor_hide_"mdr_cout_getcurattr_2mdr_cursor_getinfo_Wmdr_cout_init_mdr_cout_close_mdr_cout_cursor_show_mdr_cout_locate_mdr_cout_char_)_vmem+^YSVWUPlj^ttF]_^[à0SQY[Sƴ2[UPՈ^00ÊF&C&]UPV00ÊFMVVVVV!V$V'V*V 1V:VIVNV TVcVgVĀVĎV ĭVތmdr_cout_locate_n0N~t &/C&C]VUPVވ000ÊF0À<t:Nt&FC&/Cȉ]^SQR11ʊ6000PQVWUPPPRS0FFFV 7VJV zV ~VĄVvideo_detectega_֠l%^u@&^^~t ~t~u^^~u131%V OV SV ]V aV nTmdr_cout_char_rep_V mdr_cout_str_ mdr_cout_cls_mdr_cout_getconprops_ Q[ _term_width _term_height _savedattr _cursor_x _cursor_y _cursor_shapeӶ _vmem _small_code_ math87sÈemu87T ZftC:\SVN\MDR\cout\coutrawc.cψT0sOed"[pWC:\SVN\MDR\cout\coutrawc.c|!ձ!YC:\SVN\MDR\inc\mdr\cout.h !CODEDATABSSTLSDGROUP_TEXT(OCONSTטH CONST2H  _DATAH  R!SQRVWUPF!]_^ZY[Ûmdr_coutraw_char_C _small_code_ math87sÈemu87T ZftC:\SVN\MDR\cout\coutraws.cT0sOed"[pWC:\SVN\MDR\cout\coutraws.cl!ձ!YC:\SVN\MDR\inc\mdr\cout.h !CODEDATABSSTLSDGROUP_TEXT(W؈OCONSTטH CONST2H  _DATAH  Rmdr_coutraw_str_[SQRVW ! !_^ZY[SQRVWUPPŒ^F؎23I@3!] SV1Amdr_coutraw_crlf_mdr_coutraw_str_mdr_coutraw_puts_R _small_code_ math87sÈemu87T ZftC:\SVN\MDR\dos\dosdat2u.cT0sOed![pWC:\SVN\MDR\dos\dosdat2u.c5 5YC:\SVN\MDR\inc\mdr\dos.h !CODEDATABSSTLSDGROUP_TEXT(VوOCONSTטH CONST2H  _DATAH  Rmemset_mktime_ZSQVUƻ1ҍF%F0$HF PvFFu =u11҉]^Y[; V?VQmdr_dos_date2unix_  _small_code_ math87sÈemu87T"ZftC:\SVN\MDR\dos\dostim2h.cT0sOed![pWC:\SVN\MDR\dos\dostim2h.c1 5YC:\SVN\MDR\inc\mdr\dos.h !CODEDATABSSTLSDGROUP_TEXT(% OCONSTטH CONST2H  _DATAH  R)VWƉ׉Ȉʀ€?_^mdr_dos_time2hms_w _small_code_ math87sÈemu87T"ZftC:\SVN\MDR\dos\dosenv.c#T0sOed[pWC:\SVN\MDR\dos\dosenv.cЈ 5YC:\SVN\MDR\inc\mdr\dos.h !CODEDATABSSTLSDGROUP_TEXT(OCONSTטH CONST2H  _DATAH  R mdr_dos_psp_`SÎ&W,1[1V mdr_dos_env_a _small_code_ math87sÈemu87T"ZftC:\SVN\MDR\dos\dosgtenv.cDT0sOed![pWC:\SVN\MDR\dos\dosgtenv.c 5YC:\SVN\MDR\inc\mdr\dos.h !CODEDATABSSTLSDGROUP_TEXT(OCONSTטH CONST2H  _DATAH  R mdr_dos_env_jQVWU։^ÉVF&t`1ǀ=u6F&==u)@1F;Fu1Rlj&u=@߉ǎF‰V~&:u t@ÎFG&?t}0]_^YHVsmdr_dos_getenv_ _small_code_ math87sÈemu87T"ZftC:\SVN\MDR\dos\dosexepa.cUT0sOed![pWC:\SVN\MDR\dos\dosexepa.c 5YC:\SVN\MDR\inc\mdr\dos.h !CODEDATABSSTLSDGROUP_TEXT(W؈OCONSTטH CONST2H  _DATAH  Rmdr_dos_selfexe_[SQRVWǎ…u u041& \u‰ƀ<t=~rG@B_^ZY[V|mdr_dos_exepath_ _small_code_ math87sÈemu87T"ZftC:\SVN\MDR\dos\dospsp.cT0sOed[pWC:\SVN\MDR\dos\dospsp.cƈ 5YC:\SVN\MDR\inc\mdr\dos.h !CODEDATABSSTLSDGROUP_TEXT(& OCONSTטH CONST2H  _DATAH  R*SQVWUPFPSb![XV1]_^Y[r mdr_dos_psp_W _small_code_ math87sÈemu87T"ZftC:\SVN\MDR\dos\dosselfe.cYT0sOed![pWC:\SVN\MDR\dos\dosselfe.c 5YC:\SVN\MDR\inc\mdr\dos.h !CODEDATABSSTLSDGROUP_TEXT(6OCONSTטH CONST2H  _DATAH  R mdr_dos_env_j:SVÉЎw&?t&<u\&?u11CC^[yVmdr_dos_selfexe_ _small_code_ math87sÈemu87T"ZftC:\SVN\MDR\dos\dosgtkey.cDT0sOed![pWC:\SVN\MDR\dos\dosgtkey.c 5YC:\SVN\MDR\inc\mdr\dos.h !CODEDATABSSTLSDGROUP_TEXT(0OCONSTטH CONST2H  _DATAH  R4SQRVWUPF!udž!2 F]_^ZY[mdr_dos_getkey_ _small_code_ math87sÈemu87T"ZftC:\SVN\MDR\dos\dosgtke2.cT0sOed! ZXC:\SVN\MDR\dos\dosgtke2.c 5YC:\SVN\MDR\inc\mdr\dos.h !CODEDATABSSTLSDGROUP_TEXT(OCONSTטH CONST2H  _DATAH  RS2۴!u![Hmdr_dos_getkey2_ _small_code_ math87sÈemu87T"ZftC:\SVN\MDR\dos\doskflsh.cPT0sOed![pWC:\SVN\MDR\dos\doskflsh.c 5YC:\SVN\MDR\inc\mdr\dos.h !CODEDATABSSTLSDGROUP_TEXT(OCONSTטH CONST2H  _DATAH  RSQRVW !_^ZY[]mdr_dos_flushkeyb_ь _small_code_ math87sÈemu87T"ZftC:\SVN\MDR\dos\doskpend.cVT0sOed![pWC:\SVN\MDR\dos\doskpend.c 5YC:\SVN\MDR\inc\mdr\dos.h !CODEDATABSSTLSDGROUP_TEXT($ OCONSTטH CONST2H  _DATAH  R(SQRVWUPF !2䉆F]_^ZY[xmdr_dos_keypending_n _small_code_ math87sÈemu87T"ZftC:\SVN\MDR\dos\dosc_inh.cgT0sOed![pWC:\SVN\MDR\dos\dosc_inh.c 5YC:\SVN\MDR\inc\mdr\dos.h !CODEDATABSSTLSDGROUP_TEXT(OCONSTטH CONST2H  _DATAH  RSQRVW#%!_^ZY[o xmdr_dos_ctrlc_inhibit_8ψ _small_code_ math87sÈemu87T"ZftC:\SVN\MDR\dos\dosc_ena.crT0sOed![pWC:\SVN\MDR\dos\dosc_ena.c 5YC:\SVN\MDR\inc\mdr\dos.h !CODEDATABSSTLSDGROUP_TEXT(OCONSTטH CONST2H  _DATAH  RSQRVW3!_^ZY[Ëmdr_dos_ctrlc_enable_ _small_code_ math87sÈemu87T"ZftC:\SVN\MDR\dos\dosc_dis.cfT0sOed![pWC:\SVN\MDR\dos\dosc_dis.c 5YC:\SVN\MDR\inc\mdr\dos.h !CODEDATABSSTLSDGROUP_TEXT((OCONSTטH CONST2H  _DATAH  R,SQRVWUPF3!2!F]_^ZY[ámdr_dos_ctrlc_disable_K _small_code_ math87sÈemu87T$ZftC:\SVN\MDR\dos\dostrunm.c2T0sOed![pWC:\SVN\MDR\dos\dostrunm.c 5YC:\SVN\MDR\inc\mdr\dos.h !CODEDATABSSTLSDGROUP_TEXT(GOCONSTטH CONST2H  _DATAH  RKSQVWUF^V^u`!]_^Y[Emdr_dos_truename_? _small_code_ math87sÈemu87T$ZftC:\SVN\MDR\mouse\mouse.cT0sOed [pWC:\SVN\MDR\mouse\mouse.cQ":YC:\SVN\MDR\inc\mdr\mouse.h !CODEDATABSSTLSDGROUP_TEXT(NOCONSTטH CONST2H  _DATAH  R int86_MSRUF^V3~uF1SRUF^V3]Z[SRUFSVWUƉF^V3tFtFF]_^[SQVWUƉ1A}/FN^V V?VrV/3~tFuڋFF@1]_^Y[þVnmdr_mouse_init_mdr_mouse_show_(mdr_mouse_hide_Gmdr_mouse_getstate_Vmdr_mouse_fetchrelease_M _small_code_ math87sÈemu87T$ZftC:\SVN\MDR\mouse\mousecur.cTT0sOed#[pWC:\SVN\MDR\mouse\mousecur.c":YC:\SVN\MDR\inc\mdr\mouse.h !CODEDATABSSTLSDGROUP_TEXT(9OCONSTטH CONST2H  _DATAH  R=QVWUV^^F 33Ɋ3]_^YWmdr_mouse_setcursor_͌ _small_code_ math87sÈemu87T$ZftC:\SVN\MDR\opl\opl.chT0sOed[pWC:\SVN\MDR\opl\opl.c!,YC:\SVN\MDR\inc\mdr\bios.h @YC:\SVN\MDR\inc\mdr\opl.h !CODEDATABSSTLSDGROUP_TEXT(<OCONSTטH CONST2H _DATAH  _BSSH  L3outp_inp_mdr_opl_regwr_mdr_bios_tickswait_Sӈ0Jt0#Jt[SQR`0!0`totd 1Ҹ:9 VV&V5VEVNVTVdVmVsVyVVVV.= _op1offsets _op2offsetsmdr_opl_clear_mdr_opl_noteoff_⠺1Ҹ1Ҹ 0 0@0@0C |1ZY[SQR0Ɉ0@0@0 rR1҈0B |1ҸZSQVӊW 0MV VVVV#V-V1V;V?VIVRV^VhVrVvVVVVʹ _carr40_cacheqlW0@W 0@000W00W00W00W0`0W0`0W0 0W0 0W 00^Y[SÝYV VVV V(V/V8V?VHVOVXV_VhVoVxVVĈVVĘVVV"00䊟@‰[Ó V VV}mdr_opl_regwr_ mdr_opl_init_;mdr_opl_close_mdr_opl_clear_Amdr_opl_loadinstrument_[mdr_opl_voicevolume_  ! _op1offsets _op2offsets Y _carr40_cachee _small_code_ math87sÈemu87T$ZftC:\SVN\MDR\opl\oplnotes.c5T0sOed![pWC:\SVN\MDR\opl\oplnotes.c @YC:\SVN\MDR\inc\mdr\opl.h !CODEDATABSSTLSDGROUP_TEXT(OCONSTטH CONST2H  _DATAH  _BSSH  L _keyon_cacheYmdr_opl_regwr_SR0ڈÀðZ[QV0ƈؘ 00ƈʀ 0Љ 0ʀ°0Ӊ‰^YQV0ƈؘ ň00ˆ00M!VV.VBVFVZVvVVB?mdr_opl_noteoff_mdr_opl_noteon_mdr_opl_notebend__ _keyon_cacheM _small_code_ math87sÈemu87T$ZftC:\SVN\MDR\opl\oplimf.c&T0sOed[pWC:\SVN\MDR\opl\oplimf.cӈ @YC:\SVN\MDR\inc\mdr\opl.h !CODEDATABSSTLSDGROUP_TEXT(tOCONSTטH CONST2H  _DATAH  R _next_wakeup'mdr_opl_regwr_xSÉ?u[Ãu[1[QVW։1ɉÃr>9s )=@s .+=@w#?t G0ŠG뽉_^Y0V1VDVZVaVҐ-mdr_opl_imf_init_mdr_opl_imf_play_ V _next_wakeup _small_code_ math87sÈemu87T$ZftC:\SVN\MDR\opl\oplimfez.cCT0sOed![pWC:\SVN\MDR\opl\oplimfez.c @YC:\SVN\MDR\inc\mdr\opl.h !CODEDATABSSTLSDGROUP_TEXT(OCONSTטH CONST2H  _DATAH  _BSSH  L _imflenorg_loopsymdr_opl_imf_init_U _imfptrorg_imfptr_imflenmdr_opl_imf_play_SVWƉ׉ڃru_^É>Ɖ6)lj>6>1_^SQR>s3>u@>u4>vÉ)1ZY[՜UVV V&V,V0V6VBVIVUVaVhVlVpVtVxV|VĀVVċVďV5mdr_opl_imfeasy_init_mdr_opl_imfeasy_play_=y? _imfptrorg_imfptr _imflenorg_imflen_loops _small_code_ math87sÈemu87T$ZftC:\SVN\MDR\opl\opltigen.cGT0sOed![pWC:\SVN\MDR\opl\opltigen.c @YC:\SVN\MDR\inc\mdr\opl.h !CODEDATABSSTLSDGROUP_TEXT(eOCONSTטH CONST2H  _DATAH  RSQVƉӊDG gGĈdGgDGgDgGĈdgGıGıGĊG ĈdgGıGıGĊGĈdg G Ĉd gGĈdgGĈd 1^Y[bmdr_opl_timbre_gen_{ _small_code_ math87sÈemu87T$ZftC:\SVN\MDR\opl\opltimer.c=T0sOed![pWC:\SVN\MDR\opl\opltimer.c @YC:\SVN\MDR\inc\mdr\opl.h !CODEDATABSSTLSDGROUP_TEXT(_ЈOCONSTטH CONST2H  _DATAH  Rmdr_opl_regwr_inp_ӠcSÈ1Ҹ(u0¸ 0¸[R`u0ZúZþ VV'V6V?VHVYV0mdr_opl_timer_set_mdr_opl_timer_tick_C _small_code_ math87sÈemu87T$ZftC:\SVN\MDR\pcx\pcx.chT0sOed[pWC:\SVN\MDR\pcx\pcx.c DYC:\SVN\MDR\inc\mdr\pcx.h !CODEDATABSSTLSDGROUP_TEXT(cOCONSTטH CONST2H  _DATAH  Rbzero_fseek_fread_VWUPlj։^N1҉FF=Ft~ t~t~v~tF<t<t<t <tF)FF)F1FF~ukF Ft^^NN VV+Vefgetc_fclose_memcpy_1 |~0- = wLG0= t I1E0VFFEFEFEFE^ÀN1҉t]_^LG0FȺV)VCVZVVlÊFG|~0- = wLG0FȺÊFG |~0- = wBFȺÊFG Ar*|~0- = vSYLG0QVWUPP։FԜ V\VV$1;Lsa~?0- = wO?E0F=u`=|;<u6V?~?0- = w 1O?E0FFwy;Lw~FA1ɉȉ]_^Y VdV( mdr_pcx_anal_mdr_pcx_loadrow_' _small_code_ math87sÈemu87T$ZftC:\SVN\MDR\pcx\pcxto8.cGT0sOed[pWC:\SVN\MDR\pcx\pcxto8.c DYC:\SVN\MDR\inc\mdr\pcx.h !CODEDATABSSTLSDGROUP_TEXT(MOCONSTטH CONST2H  _DATAH  RQVWUPP׈؃~tu(]rvtUtMAu ]CvNEFF1ҋ^É1^KF<t <t$<tX 0$NN;^tK 0%NР0$N$NN;^tmKȊ 0N 00$N0$N0$N0$N0$N$NN;^tK1]_^Y_mdr_pcx_to8bpp_B _small_code_ math87sÈemu87T&ZftC:\SVN\MDR\pcx\pcxload.cT0sOed [pWC:\SVN\MDR\pcx\pcxload.cm DYC:\SVN\MDR\inc\mdr\pcx.h !CODEDATABSSTLSDGROUP_TEXT(8OCONSTטH CONST2H  _DATAH  Rmdr_pcx_loadrow_<QVWUP։FF;DwLuF1]_^Y!Vc mdr_pcx_load_ _small_code_ math87sÈemu87T&ZftC:\SVN\MDR\pktdrv\pktdrv.cT0sOed"%YC:\SVN\MDR\pktdrv\pktdrv.c.#`#YC:\SVN\MDR\inc\mdr\pktdrv.hX !CODEDATABSSTLSDGROUP_TEXT(lOCONSTטH CONST2H  _DATAH _BSSH  L% pktdrv_recv_pktdrv_getptr_8ڴ_FRAMEpktdrv_getptr_fS4ۻu!wu GG̀ 3ێË'[SQVWU00&&D~1F}J&:t11 ƒ]_^Y[SQRVW÷t`8w0 V[Vn _PKTDRV_PTR pktdrv_recv_ t މ t/0<s0&<4u&|u&0SQVWU ÉV^FFFW_HSQRVWUPFFFFFF&V VVVcViVȡVĦVĩgnFFF0V ‰^FPSQRVW32t r Ɔ_^ZY[X~t ~uF]_^ZY[øSQVWU ÉV^FFF:'VV&)V,VĒVīVıV-L$PQRV^ZYX1ESQRVWUPPPFFܜ /V5Vmdr_pktdrv_init_mdr_pktdrv_getmac_mdr_pktdrv_accesstype_Dmdr_pktdrv_getbufptr_mdr_pktdrv_send_mdr_pktdrv_free_G PKT DRVR _PKTDRV_PTR_FRAME _small_code_ math87sÈemu87T&ZftC:\SVN\MDR\rs232\rs232.cT0sOed [pWC:\SVN\MDR\rs232\rs232.c"SYC:\SVN\MDR\inc\mdr\rs232.h !CODEDATABSSTLSDGROUP_TEXT(pOCONSTטH CONST2H  _DATAH  Rinp_outp_tS=}1[=1ێÀ&G[SRúJ~G t1Z[øZ[SÍG t[RuZÉZΜ2VJVSV^VlVnDrs232_getport_ rs232_check_ rs232_write_C rs232_read_W _small_code_ math87sÈemu87T&ZftC:\SVN\MDR\sbdigi\sbdigi.cT0sOed"[pWC:\SVN\MDR\sbdigi\sbdigi.c#XYC:\SVN\MDR\inc\mdr\sbdigi.h !CODEDATABSSTLSDGROUP_TEXT(OCONSTטH CONST2H  _DATAH R strtol_\SQRVW2;t_^ZY[SQRVWU1FFF1ɉF1tHtEu  XX_^][[ZYX1SQR6C1Ҹ@1Ҹ@ZY[SQR1zVV!%*/5V9V=VBVFVPV`VeVhVkVqVuVyVVVVVVVīVįVĴV handle_clock_E6C1Ҹ@SRWZ[÷-VVV V VV!V-V0V4V Éƅt2WOu4t$WGu3u/1QGut@?GF^VFDFN^V]^Y[SQRVW$uNV:VQVVV, _fmemcpy_ _fmemset_$t_^ZY[QVUPƉ0$ FFÉ0$ FFѸPPTȉ]^YSQVWƈЃ|u0T:|u7$0 DZ ߱ NjT@Ty `VV[ video_open_video_waitvblank_video_cgalinecopy_ video_cls_% video_close_ _ffree_s @_)SRVUFDF^VDtT]^Z[VWUPƉ؈̓ É^FN~0FÉ׃& D犍&BƉ]_dVWU V.VX biostick!mdr_bios_ticks_mdr_cout_getconprops_ mdr_cout_cls_ mdr_cout_str_mdr_cout_scrollup_mdr_coutraw_puts_dosenv! dosexepa! dosselfe! dosgtke2! doskflsh! _op1offsetsmdr_pktdrv_accesstype_0mdr_pktdrv_getmac_0mdr_pktdrv_init_0rs232_getport_3sbdigi_playsample_5mdr_timer_init_8mdr_timer_reset_8 trigint_sin_; video_open_? vidrectf!Hvideo_putspritefromfile_Ivid12_setpalette_Nvid12_vramcpy_done_Umdr/PaxHeaders/sbdigi.h0000644000000000000000000000013214672035463012342 xustar0030 mtime=1726495539.327401489 30 atime=1726495547.035593727 30 ctime=1726495539.327401489 mdr/sbdigi.h0000644000175000001440000000515114672035463014175 0ustar00mateuszusers00000000000000/* * SoundBlaster routines for DSP driving * * This file is part of Mateusz' DOS Routines * Published under the terms of the MIT License, as stated below. * * Copyright (C) 2014-2024 Mateusz Viste * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to * deal in the Software without restriction, including without limitation the * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or * sell copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS * IN THE SOFTWARE. */ #ifndef MDR_SBDIGI_H #define MDR_SBDIGI_H struct sbdigi_ctx; /* initializes the SoundBlaster DSP chip * blaster must point to a BLASTER environment string (like "A220 I5 D1") * returns a pointer to a context, NULL on error * NOTE: DSP's state after initialization may or may not be muted, depending * on the exact hardware revision. use sbdigi_spkoff() to make sure it is * unmuted */ struct sbdigi_ctx *sbdigi_init(const char *blaster); /* unmutes the SoundBlaster DSP */ void sbdigi_spkon(struct sbdigi_ctx *ctx); /* mutes the SoundBlaster DSP */ void sbdigi_spkoff(struct sbdigi_ctx *ctx); /* plays a short sample * ctx - DSP context, as returned by sbdigi_init() * buf - pointer to sample data (must be PCM, 8000 Hz, mono, 8-bit unsigned * len - length of the sample, in bytes * NOTES: this routine uses DMA to transfer memory. This has two implications: * 1. the routine will return almost immediately, while the sound is playing * 2. sample data must be contained in a buffer that does NOT cross a 64K page * because DMA transfers are unable to cross 64K boundaries */ void sbdigi_playsample(struct sbdigi_ctx *ctx, void *buf, unsigned short len); /* shuts down the DSP and frees context memory */ void sbdigi_quit(struct sbdigi_ctx *ctx); #endif mdr/PaxHeaders/wave.h0000644000000000000000000000013214672035463012043 xustar0030 mtime=1726495539.327401489 30 atime=1726495547.035593727 30 ctime=1726495539.327401489 mdr/wave.h0000644000175000001440000000336014672035463013676 0ustar00mateuszusers00000000000000/* * WAVE-loading routines * * This file is part of Mateusz' DOS Routines * Published under the terms of the MIT License, as stated below. * * Copyright (C) 2014-2024 Mateusz Viste * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to * deal in the Software without restriction, including without limitation the * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or * sell copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS * IN THE SOFTWARE. */ #ifndef MDR_WAVE_H #define MDR_WAVE_H struct wave_t { unsigned short format; unsigned short channels; unsigned short rate; unsigned short bitdepth; unsigned long dataoffset; unsigned long len; }; /* looks at an open WAVE file and fills the wav structure with related * information (format, number of channels, bit depth, data rate, etc) * returns 0 on success */ int wave_anal(struct wave_t *wav, FILE *fd); #endif mdr/PaxHeaders/unzip.h0000644000000000000000000000013214672035463012246 xustar0030 mtime=1726495539.327401489 30 atime=1726495547.035593727 30 ctime=1726495539.327401489 mdr/unzip.h0000644000175000001440000000471014672035463014101 0ustar00mateuszusers00000000000000/* * Function to iterate through files in a ZIP archive. * * This file is part of Mateusz' DOS Routines * Published under the terms of the MIT License, as stated below. * * Copyright (C) 2014-2024 Mateusz Viste * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to * deal in the Software without restriction, including without limitation the * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or * sell copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS * IN THE SOFTWARE. */ #ifndef MDR_UNZIP #define MDR_UNZIP #include /* FILE * */ #define ZIP_FLAG_ISADIR 1 #define ZIP_FLAG_ENCRYPTED 2 #define ZIP_METH_STORE 0 #define ZIP_METH_DEFLATE 8 struct mdr_zip_item { unsigned long filelen; unsigned long compressedfilelen; unsigned long crc32; unsigned long dataoffset; /* offset in the file where compressed data starts */ unsigned long nextidxoffset; /* offset in the file of the next zip record, used by mdr_zip_iter() */ unsigned short dosdate; /* datestamp of the file (DOS packed format) */ unsigned short dostime; /* timestamp of the file (DOS packed format) */ unsigned short compmethod; /* compression method (ZIP_METH_xxx) */ unsigned char flags; /* see ZIP_FLAG_xxx above */ char fname[256]; /* filename */ }; /* returns next item found in zip file. this is supposed to be called * iteratively, passing the previous mdr_zipitem struct each time (z must be * all zeroed out on first call). * returns 0 on success, neg on error, 1 on end of archive */ int mdr_zip_iter(struct mdr_zip_item *z, FILE *fd); #endif mdr/PaxHeaders/video.h0000644000000000000000000000013214672035463012207 xustar0030 mtime=1726495539.327401489 30 atime=1726495547.035593727 30 ctime=1726495539.327401489 mdr/video.h0000644000175000001440000001023214672035463014036 0ustar00mateuszusers00000000000000/* * video library - provides a few functions for mode 4 and 13h programming. * * This file is part of Mateusz' DOS Routines * Published under the terms of the MIT License, as stated below. * * Copyright (C) 2014-2024 Mateusz Viste * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to * deal in the Software without restriction, including without limitation the * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or * sell copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS * IN THE SOFTWARE. */ #ifndef MDR_VIDEO_H #define MDR_VIDEO_H #define VIDEO_DBUF 1 struct video_handler { unsigned char far *dbuf; int flags; int mode; unsigned char lastmode; }; /* returns 0 if no VGA has been detected, non-zero otherwise */ int video_detectvga(void); /* returns 0 if no EGA has been detected, non-zero otherwise */ int video_detectega(void); /* init video mode. either 0x04 for CGA or 0x13 for VGA */ struct video_handler *video_open(int mode, int flags); /* reads a screen dump from file and puts it to the screen buffer */ void video_file2screen(struct video_handler *handler, char *file); /* load count colors of palette from array of rgb triplets */ void video_loadpal(const unsigned char *pal, int count, int offset); /* Wait until VBLANK */ void video_waitvblank(void); void video_flip(struct video_handler *handler); /* copies line ysrc over ydst in CGA mode memory */ void video_cgalinecopy(struct video_handler *handler, int ydst, int ysrc); /* clear screen using color */ void video_cls(struct video_handler *handler, unsigned char color); /* renders a sprite of width and height dimensions onscreen, starting at specified x/y location coloffset is an offset to add to color indexes, while transp is the index of the transparent color (set to -1 if none) */ void video_putsprite(struct video_handler *handler, unsigned char *sprite, int x, int y, int width, int height, int coloffset, int transp, int maxcol); /* same as video_putsprite(), but reads the sprite from a file */ void video_putspritefromfile(struct video_handler *handler, char *file, long foffset, int x, int y, int width, int height, int coloffset, int transp, int maxcol); void video_close(struct video_handler *handler); void video_rputpixel(struct video_handler *handler, int x, int y, unsigned char col, int repeat); /* render a horizontal line of length len starting at x/y */ void video_hline(struct video_handler *handler, int x, int y, int len, unsigned char color); /* render a vertical line of length len starting at x/y */ void video_vline(struct video_handler *handler, int x, int y, int len, unsigned char color); void video_line(struct video_handler *handler, int x1, int y1, int x2, int y2, unsigned char color); void video_rect(struct video_handler *handler, int x, int y, int width, int height, unsigned char color); /* renders a rectangle on screen, at position x/y, filled with color */ void video_rectfill(struct video_handler *handler, int x, int y, int width, int height, unsigned char color); void video_setpalette(unsigned char index, unsigned char r, unsigned char g, unsigned char b); void video_getpalette(unsigned char index, unsigned char *r, unsigned char *g, unsigned char *b); #endif mdr/PaxHeaders/xms.h0000644000000000000000000000013214672035463011710 xustar0030 mtime=1726495539.327401489 30 atime=1726495547.035593727 30 ctime=1726495539.327401489 mdr/xms.h0000644000175000001440000000430014672035463013536 0ustar00mateuszusers00000000000000/* * XMS driver * * This file is part of Mateusz' DOS Routines * Published under the terms of the MIT License, as stated below. * * Copyright (C) 2014-2024 Mateusz Viste * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to * deal in the Software without restriction, including without limitation the * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or * sell copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS * IN THE SOFTWARE. */ #ifndef MDR_XMS_H #define MDR_XMS_H struct xms_struct { unsigned int handle; long memsize; /* allocated memory size, in bytes */ }; /* checks if a XMS driver is installed, inits it and allocates a memory block of memsize K-bytes. * if memsize is 0, then the maximum possible block will be allocated. * returns the amount of allocated memory (in K-bytes) on success, 0 otherwise. */ unsigned int xms_init(struct xms_struct *xms, unsigned int memsize); /* free XMS memory */ void xms_close(struct xms_struct *xms); /* copies a chunk of memory from conventional memory into the XMS block. returns 0 on sucess, non-zero otherwise. */ int xms_push(struct xms_struct *xms, void far *src, unsigned int len, long xmsoffset); /* copies a chunk of memory from the XMS block into conventional memory */ int xms_pull(struct xms_struct *xms, long xmsoffset, void far *dst, unsigned int len); #endif mdr/PaxHeaders/pktdrv.h0000644000000000000000000000013214672035463012413 xustar0030 mtime=1726495539.327401489 30 atime=1726495547.035593727 30 ctime=1726495539.327401489 mdr/pktdrv.h0000644000175000001440000000527214672035463014252 0ustar00mateuszusers00000000000000/* * Packet driver library for Watcom C * * This file is part of Mateusz' DOS Routines * Published under the terms of the MIT License, as stated below. * * Copyright (C) 2014-2024 Mateusz Viste * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to * deal in the Software without restriction, including without limitation the * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or * sell copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS * IN THE SOFTWARE. */ #ifndef MDR_PKTDRV_H #define MDR_PKTDRV_H /* intitializes packet driver, returns the interrupt number of the packet * driver used, or 0 on error. * pktint = interrupt number to look at (0 = automatic scan) * this function is mandatory before using any other of the pktdrv functions */ unsigned char mdr_pktdrv_init(unsigned char pktint); /* registers a packet driver handle for given ethertype (or all ethertypes if * value is 0). returns a handle on success, 0xffff otherwise. */ unsigned short mdr_pktdrv_accesstype(unsigned short ethertype); /* get my own MAC addr. target MUST point to a space of at least 6 chars */ void mdr_pktdrv_getmac(unsigned char *dst, unsigned short handle); /* returns a pointer to the frame buffer * the frame buffer starts with a single word (16-bit) that provides the status * of the buffer: * 0 = EMPTY * bit 0x8000 set = WRITE IN PROGRESS (do not touch) * other value = length of frame that follows, in bytes * you do not need to call this function more than one time. Once you have the * frame buffer address you can use it indefinitely, it never changes. */ void *mdr_pktdrv_getbufptr(void); /* sends a frame of len bytes, returns 0 on success */ int mdr_pktdrv_send(const void *pkt, unsigned short len); /* frees the given handle (protocol) of the packet driver */ void mdr_pktdrv_free(unsigned short handle); #endif mdr/PaxHeaders/cout.h0000644000000000000000000000013214672035463012053 xustar0030 mtime=1726495539.327401489 30 atime=1726495547.035593727 30 ctime=1726495539.327401489 mdr/cout.h0000644000175000001440000001011514672035463013702 0ustar00mateuszusers00000000000000/* * Console output * * This file is part of Mateusz' DOS Routines * Published under the terms of the MIT License, as stated below. * * Copyright (C) 2014-2024 Mateusz Viste * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to * deal in the Software without restriction, including without limitation the * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or * sell copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS * IN THE SOFTWARE. */ #ifndef MDR_COUT #define MDR_COUT /* inits the subsystem, fills arguments with: * w = screen width * h = screen height * Any of these arguments may be passed as NULL * Returns a color flag (0=mono, 1=color) */ unsigned char mdr_cout_init(unsigned char *w, unsigned char *h); /* get current attribute value under cursor and returns it */ int mdr_cout_getcurattr(void); void mdr_cout_close(void); void mdr_cout_cursor_hide(void); void mdr_cout_cursor_show(void); /* gets cursor's position on screen (row, column) and shape */ void mdr_cursor_getinfo(unsigned char *column, unsigned char *row, unsigned short *shape); void mdr_cout_locate(unsigned char row, unsigned char column); /* print a single character on screen */ void mdr_cout_char(unsigned char y, unsigned char x, char c, unsigned char attr); /* print a single character on screen, repeated count times (count=0 prints nothing) */ void mdr_cout_char_rep(unsigned char y, unsigned char x, char c, unsigned char attr, unsigned char count); /* print a nul-terminated string on screen, up to maxlen characters * returns the number of characters actually displayed */ unsigned char mdr_cout_str(unsigned char y, unsigned char x, const char *s, unsigned char attr, unsigned char maxlen); /* clears screen, filling it with a single color attribute */ void mdr_cout_cls(unsigned char colattr); /* scrolls a rectangle of screen by nlines up, starting at upper left corner * [row_high,col_high] and ending at lower right corner [row_low,col_low]. * blank area is filled with blanks and attribute colattr. if nlines is 0 then * entire screen is scrolled. */ void mdr_cout_scrollup(unsigned char colattr, unsigned char row_high, unsigned char col_high, unsigned char row_low, unsigned char col_low, unsigned char nlines); /* provides properties of the current video mode: * termwidth = terminal's width * termheight = terminal's height * colorflag = mono/color flag (0=mono 1=color) * NOTE: all arguments must be valid pointers (NULL not allowed) */ void mdr_cout_getconprops(unsigned char *termwidth, unsigned char *termheight, unsigned char *colorflag); /***************************************************************************** * functions below do not need mdr_cout_init() initialization, they can be * * used to output data to console right away, as they use DOS services. * *****************************************************************************/ /* output a single character to console */ void mdr_coutraw_char(char c); /* output a nul-terminated string */ void mdr_coutraw_str(const char *s); /* same as above, but followed with a CR/LF line terminator */ void mdr_coutraw_puts(const char *s); /* outputs a DOS-style (CR/LF) newline to console */ void mdr_coutraw_crlf(void); #endif ./PaxHeaders/core.c0000644000000000000000000000013214672035463011377 xustar0030 mtime=1726495539.327401489 30 atime=1726495547.035593727 30 ctime=1726495539.327401489 core.c0000644000175000001440000011300014672035463013066 0ustar00mateuszusers00000000000000/* * ethflopd is serving files through the ethflop protocol. Runs on Linux. * * http://ethflop.sourceforge.net * * ethflopd is distributed under the terms of the MIT License, as listed * below. * * Copyright (C) 2019-2024 Mateusz Viste * * Permission to use, copy, modify, and/or distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY * SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR * IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ /* struct dirent, used by scandir() */ #if defined(__DOS__) #include #include #include /* open(), read(), write(), close() */ #define htobe16(x) (((x & 0x00ff) << 8) | ((x & 0xff00) >> 8)) #define le16toh(x) (x) #define htole16(x) (x) #else #include #include /* htobe16(), be16toh(), etc */ #endif #include #include /* flags for open() */ #include #include /* PATH_MAX and such */ #include #include /* mempcy() */ #include /* uint16_t, uint32_t */ #include /* realpath() */ #include /* clock_t */ #include /* close(), getopt(), optind */ /* O_BINARY is a historical DOS flag that could be passed to open(), it no * longer exists on modern systems so I am just defining it as a no-op so the * file-handling code does not require any special cases or ifdefs */ #if !defined(O_BINARY) #define O_BINARY 0 #endif #include "core.h" /* set to 1 to enable DEBUG mode (lots of printf calls) */ #define DEBUG 0 /* set to percentage of simulated packet loss (test purposes only!) */ #define SIMLOSS_INP 0 /* % of INPUT packets likely to get lost */ #define SIMLOSS_OUT 0 /* % of OUTPUT packets likely to get lost */ #if DEBUG > 0 #define DBG printf #else #define DBG(...) #endif /* list of current clients */ static struct cliententry *glob_clist; /* creates a file, writes a 16K header to it, followed by 16K of zeroed data. * returns 0 on success, errno otherwise * this function destroys hdr16k! */ static int img_create(const char *fname, unsigned char *hdr16k, unsigned short sectors) { int fd; int i; fd = open(fname, O_CREAT | O_WRONLY | O_BINARY | O_EXCL, S_IRUSR | S_IWUSR); if (fd == -1) return(errno); /* apply boot sector and FAT to floppy image followed by 16K of zeroed data * to make sure all root entries are zeroed */ for (i = 0; i < 2; i++) { if (i == 1) memset(hdr16k, 0, 16384); if (write(fd, hdr16k, 16384) != 16384) goto ERRNO_CLOSE_QUIT; } /* if the image is a standard floppy size (up to 2.88M) then allocate the * entire space now, so the user gets standard images that he may use also * with other software. Non-standard sizes are kept thin-provisionned so * they are very fast to generate. Otherwise generating a 31M image was * sometimes generating timeouts on the DOS version of ethflopd because the * server was busy for a few seconds initializing the image. */ if (sectors <= 5760) { /* 5760 sectors (of 512b) is 2.88M */ /* NOTE 1: this filesize expansion is NOT zeroing data on some platforms * (typically: DOS). It is not a problem but it is the reason why the root * entries above had to be explicitely initialized to zero. * NOTE 2: lseek() must be used instead of fseek(), since the latter does * not allow setting the pointer beyond EOF */ if (lseek(fd, ((off_t)sectors * 512) - 1, SEEK_SET) == -1l) goto ERRNO_CLOSE_QUIT; /* write one byte to make sure it all worked. In theory writing 0 bytes * should also work, but such write never returns an error. */ #if defined(__DOS__) { /* use _dos_write() because write() is VERY slow (zeroes data, probably) */ unsigned byteswritten; if (_dos_write(fd, "", 1, &byteswritten) != 0) goto ERRNO_CLOSE_QUIT; if (byteswritten != 1) { errno = ENOSPC; goto ERRNO_CLOSE_QUIT; } } #else if (write(fd, "", 1) != 1) goto ERRNO_CLOSE_QUIT; #endif } close(fd); return(0); ERRNO_CLOSE_QUIT: { int err = errno; close(fd); unlink(fname); return(err); } } static unsigned char MYMAC[6]; static const struct { unsigned char md; /* media descriptor */ unsigned char secttrack; /* sectors per track (max. 62) */ unsigned char cylinders; /* cylinders (or tracks per side, max. 255) */ unsigned char heads; /* heads (sides) */ unsigned char maxRootEntries; /* max number of FAT12 root entries */ unsigned char clustersz; /* cluster size, in sectors (FAT12 supports up to 4084 clusters) */ unsigned char fatsz; /* single FAT size, in sectors */ unsigned short totsectors; /* total sectors count */ char *description; /* human description, used for the help message */ /* MD sect trk hd root clustsz fatsz totsectors */ } FDPARMS[] = {{0xFD, 9, 40, 2, 112, 2, 2, 720, "360K (1983)"}, {0xF9, 9, 80, 2, 112, 2, 3, 1440, "720K (1986)"}, {0xF9, 15, 80, 2, 224, 1, 7, 2400, "1.2M (1984, IBM AT)"}, {0xF0, 18, 80, 2, 224, 1, 9, 2880, "1.44M, the classic 3.5\" floppy since 1987"}, {0xF0, 21, 80, 2, 16, 4, 3, 3360, "1.68M (DMF), big clusters and tiny root!"}, {0xF0, 21, 82, 2, 16, 4, 3, 3444, "1.72M (DMF), big clusters and tiny root!"}, {0xF0, 36, 80, 2, 224, 2, 9, 5760, "2.88M, a 1991 invention that never caught on"}, {0xF0, 60, 80, 2, 224, 4, 8, 9600, "4.8M, custom format"}, {0xF0, 60, 135, 2, 224, 4, 12, 16200, "8.1M, custom format"}, {0xF0, 60, 160, 2, 224, 8, 8, 19200, "9.6M, custom format"}, {0xF0, 62, 250, 2, 224, 8, 12, 31000, "15.5M, custom format"}, {0xF0, 62, 250, 4, 224, 16, 12, 62000, "31M, custom format"}, {0x00, 0, 0, 0, 0, 0, 0, 0, ""}}; /* end */ /* some calculations * totsectors = sect * trk * hd * numOfClusters = (totsectors - 1 - (2*fatsz)) / clustsz * fatsz = (numOfClusters * 1.5) / 512 */ /* generates a formatted MAC address printout and returns a static buffer */ static char *printmac(const unsigned char *b) { static char macbuf[18]; sprintf(macbuf, "%02X:%02X:%02X:%02X:%02X:%02X", b[0], b[1], b[2], b[3], b[4], b[5]); return(macbuf); } /* returns the FDPARMS index matching a size of sz KiB, or -1 if not found */ static int getFDPARMSidbysize(unsigned short sz) { int i; for (i = 0;; i++) { if (FDPARMS[i].md == 0) return(-1); /* reached end of list */ if (FDPARMS[i].totsectors == sz * 2) return(i); } } /* creates a new 16K header for floppy image in memory, by generating a * boot sector and FAT-12 structure. sz is the expected size, in KiB * returns zero on error, disk size (in sectors) otherwise */ static unsigned short floppygen(unsigned char *dst, unsigned short sz) { int type; /* validate dst */ if (dst == NULL) return(0); /* translate sz to type id */ type = getFDPARMSidbysize(sz); if (type < 0) return(0); /* invalid type */ /* zero out the header */ memset(dst, 0, 16384); /* BOOT SECTOR */ dst[0] = 0xEB; /* jmp */ dst[1] = 0xFE; /* jmp */ dst[2] = 0x90; /* jmp */ memcpy(dst + 3, "MSDOS5.0", 8); /* OEM sig */ dst[0x0B] = 0; /* bytes per sect LSB */ dst[0x0C] = 2; /* bytes per sect MSB */ dst[0x0D] = FDPARMS[type].clustersz; /* cluster size, in number of sectors */ dst[0x0E] = 1; /* reserved logical sectors (number of sectors before 1st FAT) */ dst[0x0F] = 0; /* reserved logical sectors */ dst[0x10] = 2; /* num of FATs */ dst[0x11] = FDPARMS[type].maxRootEntries; /* max num of root dirs, must be multiple of 16 (LSB) */ dst[0x12] = 0; /* max num of root dirs, must be multiple of 16 (MSB) */ dst[0x13] = (uint8_t)(FDPARMS[type].totsectors); /* total sectors, LSB */ dst[0x14] = (uint8_t)(FDPARMS[type].totsectors >> 8); /* tot sectors, MSB */ dst[0x15] = FDPARMS[type].md; /* media descriptor */ dst[0x16] = FDPARMS[type].fatsz; /* sectors per FAT, LSB */ dst[0x17] = 0; /* sectors per FAT, MSB */ dst[0x18] = FDPARMS[type].secttrack; /* sectors per track (LSB) */ dst[0x19] = 0; /* sectors per track (MSB) */ dst[0x1A] = FDPARMS[type].heads; /* heads count (LSB) */ dst[0x1B] = 0; /* heads count (MSB) */ /* 1C 1D 1E 1F - hidden sectors */ /* 20 21 22 23 - total sectors (extended) */ /* 24 25 - reserved bytes */ dst[0x26] = 0x29; /* 0x29 means that volid, vol label and fsname are present (below) */ dst[0x27] = (uint8_t)(time(NULL)); /* volume id */ dst[0x28] = (uint8_t)(time(NULL) >> 8); /* volume id */ dst[0x29] = (uint8_t)(time(NULL) >> 16); /* volume id */ dst[0x2A] = (uint8_t)(time(NULL) >> 24); /* volume id */ memcpy(dst + 0x2B, "NO NAME ", 11); /* volume label (2B 2C 2D 2E) */ memcpy(dst + 0x36, "FAT12 ", 8); /* filesystem name */ /* 448 bytes of boot code */ dst[0x1FE] = 0x55; /* boot sig */ dst[0x1FF] = 0xAA; /* boot sig */ /* empty FAT tables: they simply start by a 3-bytes signature. a FAT sig is * two (12 bit) cluster entries: first byte of first entry is a copy of the * media descriptor, all other bits are set to 1. */ dst[0x200] = FDPARMS[type].md; /* 1st FAT starts right after bootsector */ dst[0x201] = 0xff; dst[0x202] = 0xff; /* 2nd FAT is at 512 + (sectorsperfat * 512), ie. it follows the 1st FAT table */ memcpy(dst + 0x200 + (FDPARMS[type].fatsz * 512), dst + 0x200, 3); /* */ return(FDPARMS[type].totsectors); } static struct cliententry *findorcreateclient(const uint8_t *mac, char *log) { struct cliententry *e; for (e = glob_clist; e != NULL; e = e->next) { if (memcmp(mac, e->mac, 6) == 0) return(e); } /* nothing found */ e = calloc(1, sizeof(struct cliententry)); if (e == NULL) { strcpy(log, "failed to init a new client entry (out of memory)"); return(NULL); /* out of memory */ } memcpy(e->mac, mac, 6); e->fd = -1; e->next = glob_clist; glob_clist = e; return(e); } /* turns a character c into its up-case variant */ static char upchar(char c) { if ((c >= 'a') && (c <= 'z')) c -= ('a' - 'A'); return(c); } /* turns a character c into its lo-case variant */ static char lochar(char c) { if ((c >= 'A') && (c <= 'Z')) c += ('a' - 'A'); return(c); } /* turns a string into all-upper-case characters, up to n chars max */ static void upstring(char *s, unsigned short n) { while ((n-- != 0) && (*s != 0)) { *s = upchar(*s); s++; } } /* turns a string into all-lower-case characters, up to n chars max */ static void lostring(char *s, unsigned short n) { while ((n-- != 0) && (*s != 0)) { *s = lochar(*s); s++; } } /* converts a CHS tuple into an LBA sector id */ static unsigned short chs2lba(unsigned short c, unsigned short h, unsigned short s, unsigned short totheads, unsigned short sectspertrack) { unsigned short sectid; sectid = (c * totheads + h) * sectspertrack + (s - 1); return(sectid); } /* generates a human message about server's status */ static void fill_status_msg(void *dst, const struct cliententry *ce) { char *ptr = (char *)dst; ptr += sprintf(ptr, "ethflop server: %s\r\n", printmac(MYMAC)); if (ce->driveid > 1) { /* ethflop 0.6 and 0.6.1 did not provide the driveid */ ptr += sprintf(ptr, "virt. floppy: "); } else { ptr += sprintf(ptr, "virt. floppy in drive %c: ", 'A' + ce->driveid); } if (ce->curflopid[0] != 0) { ptr += sprintf(ptr, "%s (%uK", ce->curflopid, ce->sectcount >> 1); if (ce->ro != 0) ptr += sprintf(ptr, ", write-protected"); ptr += sprintf(ptr, ")\r\n"); } else { ptr += sprintf(ptr, "\r\n"); } *ptr = '$'; } static int process_data(struct FRAME *frame, struct cliententry *ce, char *log) { unsigned short sid; /* sector id */ /* switch src and dst addresses so the reply header is ready */ memcpy(frame->dmac, frame->smac, 6); /* copy source mac into dst field */ memcpy(frame->smac, MYMAC, 6); /* copy my mac into source field */ ce->driveid = frame->driveid; /* refresh the client's driveid */ /* decode query type */ switch(frame->ax >> 8) { case 0x00: /* DISK RESET - special case: write current FLOPID (8 chars) in frame's DATA and a human text message in DATA+100h. This query is used in only one case: when ethflop is being loaded and discovers the server */ memcpy(frame->data, ce->curflopid, 8); fill_status_msg(frame->data + 0x100, ce); sprintf(log, "client connected: %s", printmac(ce->mac)); return(0); case 0x02: /* READ SECTOR #sectnum FROM CHS CH:DH:CL*/ case 0x03: /* WRITE SECTOR #sectnum FROM CHS CH:DH:CL */ sid = chs2lba(frame->cx >> 8, frame->dx >> 8, frame->cx & 0xff, ce->chs_totheads, ce->chs_sectspertrack) + frame->sectnum; if (ce->fd == -1) { sprintf(log, "read or write attempt at empty drive"); frame->ax = 0x4000; /* error 'seek failed' + 0 sectors read/written */ return(0); } if (sid >= ce->sectcount) { sprintf(log, "read or write attempt past last sector (%u > %u)", sid, ce->sectcount - 1); frame->ax &= 0x00ff; frame->ax |= 0x4000; /* ah=40h = seek failed */ return(0); } /* position the file pointer at the requested place. this may be beyond * the file size if the image is thin-provisionned, hence it is important * to use lseek() and not fseek() as the latter does not allow to set the * file pointer beyond EOF. */ if (lseek(ce->fd, (off_t)sid * 512, SEEK_SET) == -1) { sprintf(log, "lseek() failed: %s", strerror(errno)); frame->ax &= 0x00ff; frame->ax |= 0x4000; /* ah=40h = seek failed */ return(0); } if ((frame->ax >> 8) == 0x03) { /* WRITE OP */ if (ce->ro != 0) { sprintf(log, "attempt to write to a write-protected disk"); frame->ax &= 0x00ff; frame->ax |= 0x0300; /* ah=3 = disk is write protected */ return(0); } if (write(ce->fd, frame->data, 512) != 512) { sprintf(log, "fwrite() failure: %s (%s, sect %u)", strerror(errno), ce->curflopid, sid); frame->ax &= 0x00ff; frame->ax |= 0x0400; /* ah=4 = sector not found / read error */ return(0); } if (sid > ce->lastimgsector) ce->lastimgsector = sid; /* update last actual sector in img if needed ("moving the thin provsionning marker") */ } else { /* READ OP */ /* if reading past actually provisionned sectors then return an * all-zeroes block */ if (sid > ce->lastimgsector) { memset(frame->data, 0, 512); } else { if (read(ce->fd, frame->data, 512) != 512) { sprintf(log, "read() failure: %s (%s, sect %u)", strerror(errno), ce->curflopid, sid); frame->ax &= 0x00ff; frame->ax |= 0x0400; /* ah=4 = sector not found / read error */ return(0); } } } frame->ax = frame->sectnum + 1; /* al = sectors read, ah = 0 (success) */ return(0); case 0x04: /* VERIFY - in fact this was never meant to actually verify data. always succeeds */ if (ce->fd == -1) { sprintf(log, "verify attempt at empty drive"); frame->ax = 0x4000; /* error 'seek failed' + 0 sectors verified */ return(0); } sid = chs2lba(frame->cx >> 8, frame->dx >> 8, frame->cx & 0xff, ce->chs_totheads, ce->chs_sectspertrack); /* check how many sectors are valid */ if (sid + (frame->ax & 0xff) > ce->sectcount) { unsigned short validsectors; if (sid > ce->sectcount) { validsectors = 0; } else { validsectors = (ce->sectcount - sid) + 1; } frame->ax = 0x4000 | validsectors; /* error 'seek failed' + validsectors verified */ return(0); } frame->ax &= 0x00ff; /* al = sectors verified, ah = 0 (success) */ return(0); case 0x15: /* GET DISK TYPE */ /* this routine is buggy - it returns AH=0 on success, while RBIL * says that success is indicated by CF=0 but AH should report a 0x02 * code in AH... I can't do this sadly because ethflop assumes ah=0 for * success, and would set CF for any other value */ if (ce->fd == -1) { frame->ax &= 0x00ff; frame->ax |= 0x4000; /* ah=31h - no media in drive */ return(0); } frame->ax &= 0x00ff; frame->cx = 0; /* number of sectors, high word */ frame->dx = ce->sectcount; /* number of sectors, low word */ return(0); case 0x16: /* DETECT DISK CHANGE */ frame->ax &= 0x00ff; if (ce->diskchangeflag != 0) { frame->ax |= 0x0600; /* AH=6 - change line active */ ce->diskchangeflag = 0; /* reset the flag so it's reported only once */ } return(0); case 0x20: /* GET CURRENT MEDIA FORMAT */ if (ce->fd == -1) { frame->ax &= 0x00ff; frame->ax |= 0x3100; /* set AH to error "media not present" */ return(0); } if (ce->sectcount == 1440) { frame->ax = 0x0003; /* AH=0 (success), AL=media type is 720K */ return(0); } if (ce->sectcount == 2880) { frame->ax = 0x0004; /* AH=0 (success), AL=media type is 1.44M */ return(0); } if (ce->sectcount == 5760) { frame->ax = 0x0006; /* AH=0 (success), AL=media type is 2.88M */ return(0); } if (ce->sectcount == 720) { frame->ax = 0x000C; /* AH=0 (success), AL=media type is 360K */ return(0); } if (ce->sectcount == 2400) { frame->ax = 0x000D; /* AH=0 (success), AL=media type is 1.2M */ return(0); } frame->ax &= 0x00ff; frame->ax |= 0x3200; /* ah=30h = drive does not support media type */ sprintf(log, "unable to recognize media type (%u sectors)", ce->sectcount); return(0); } /* set ah to error 01 */ frame->ax &= 0x00ff; frame->ax |= 0x0100; return(0); } /* parse data looking for a cmd, arg and arg2. returns number of arguments, or -1 on error */ static int parseargs(char *cmd, int cmdsz, char *arg, char *arg2, int argsz, const char *data) { int i, l, arglen = 0, arglen2 = 0, cmdlen = 0; int gotcmd = 0, gotarg = 0; l = data[0]; /* printf("l=%d\n", l); */ for (i = 1; i <= l; i++) { /* printf("i=%d ; data[i]='%c'\n", i, data[i]); */ if (data[i] == ' ') { if (cmdlen > 0) gotcmd = 1; if (arglen > 0) gotarg = 1; if (arglen2 > 0) break; continue; /* skip spaces */ } if (gotcmd == 0) { cmd[cmdlen++] = data[i]; } else if (gotarg == 0) { arg[arglen++] = data[i]; } else { arg2[arglen2++] = data[i]; } if (cmdlen > cmdsz) return(-1); if (arglen > argsz) return(-1); if (arglen2 > argsz) return(-1); } cmd[cmdlen] = 0; arg[arglen] = 0; arg2[arglen2] = 0; DBG(" cmd='%s' arg='%s' arg2='%s'\n", cmd, arg, arg2); if (arglen2 > 0) return(3); if (arglen > 0) return(2); if (cmdlen > 0) return(1); return(0); } static int validatediskname(const char *n) { int i; if ((n == NULL) || (*n == 0)) return(-1); for (i = 0; n[i] != 0; i++) { if (i == 8) return(-1); if ((n[i] >= 'a') && (n[i] <= 'z')) continue; if ((n[i] >= 'A') && (n[i] <= 'Z')) continue; if ((n[i] >= '0') && (n[i] <= '9')) continue; if (n[i] == '_') continue; if (n[i] == '-') continue; if (n[i] == '&') continue; return(-1); } return(0); } static char *disk2fname(const char *dname) { static char fname[16]; snprintf(fname, sizeof(fname), "%s.img", dname); lostring(fname, sizeof(fname)); return(fname); } static const struct cliententry *findcliententrybywritelock(const char *dname) { const struct cliententry *ce; for (ce = glob_clist; ce != NULL; ce = ce->next) { if ((ce->ro == 0) && (strcmp(ce->curflopid, dname) == 0)) return(ce); } return(NULL); } static const struct cliententry *findcliententrybylock(const char *dname) { const struct cliententry *ce; for (ce = glob_clist; ce != NULL; ce = ce->next) { if (strcmp(ce->curflopid, dname) == 0) return(ce); } return(NULL); } /* used by imgfilelist to sort the filenames of floppy images */ static int sortfilelist(const void *f1, const void *f2) { unsigned char i; for (i = 0; i < 8; i++) { if (*(unsigned char *)f1 > *(unsigned char *)f2) return(1); if (*(unsigned char *)f1 < *(unsigned char *)f2) return(-1); f1 = (char *)f1 + 1; f2 = (char *)f2 + 1; } return(0); } unsigned short ethflop_imgfilelist(char *s, unsigned short sz) { DIR *dir; struct dirent *d; unsigned short slen; unsigned short count = 0; char *s_start = s; /* save it for later */ dir = opendir("."); if (dir == NULL) return(0); for (;;) { d = readdir(dir); if (d == NULL) break; #ifdef __WATCOMC__ if (d->d_attr & _A_SUBDIR) continue; #else if (d->d_type == DT_DIR) continue; #endif slen = strlen(d->d_name); if (slen < 5) continue; /* A.IMG */ if (strcasecmp(d->d_name + slen - 4, ".img") != 0) continue; if (slen > 12) continue; /* an image must be 8+3 compliant */ slen -= 4; d->d_name[slen] = 0; /* truncate extension */ /* not enough space left: overwrite last string with an error message * msg must be 10 chars and has to start with an ascii char > 'Z' so it * stays at the end of the list after sorting */ if (sz < 12) { sprintf(s - 10, "[too long]"); break; } /* upcase all files */ upstring(d->d_name, 8); /* Each filename is NUL-padded to 10 bytes so I do not have to worry about * columns: DOS will wrap it nicely at either 40 or 80 columns. Also, * buffer becomes a list of 10-bytes elements, makes sorting easy. */ memset(s, 0, 10); strcpy(s, d->d_name); s += 10; sz -= 10; count++; } /* sort the list */ qsort(s_start, count, 10, sortfilelist); /* DOS terminator */ *s = '$'; closedir(dir); return(count); } /* returns 0 if file f does not exist, 1 otherwise */ static int fileexists(const char *f) { int fd; fd = open(f, O_RDONLY); if (fd == -1) return(0); close(fd); return(1); } int ethflop_eject(struct cliententry *ce, char *msg) { if (ce->fd == -1) { sprintf(msg, "ERROR: no virtual floppy loaded$"); return(-1); } close(ce->fd); ce->fd = -1; ce->sectcount = 0; ce->ro = 0; sprintf(msg, "Disk %s ejected$", ce->curflopid); memset(ce->curflopid, 0, sizeof(ce->curflopid)); return(0); } int ethflop_insert(struct cliententry *ce, const char *arg, char *msg, unsigned char roflag) { int i; const struct cliententry *sce; unsigned char buff[22]; /* enough to read the tot sectors in boot sector */ if (arg[0] == 0) { sprintf(msg, "ERROR: you must specify a diskname$"); return(-1); } if (validatediskname(arg) != 0) { sprintf(msg, "ERROR: specified disk name is invalid (%s)$", arg); return(-1); } if (ce->fd != -1) { sprintf(msg, "ERROR: you must first eject your current virtual floppy (%s)$", ce->curflopid); return(-1); } /* if mounting read-only then check that nobody has write access */ if (roflag != 0) { sce = findcliententrybywritelock(arg); if (sce != NULL) { sprintf(msg, "ERROR: disk '%s' is write-locked by %s'$", arg, printmac(sce->mac)); return(-1); } } else { /* if mounting with write access: nobody else is allowed */ sce = findcliententrybylock(arg); if (sce != NULL) { sprintf(msg, "ERROR: disk '%s' is being used by %s'$", arg, printmac(sce->mac)); return(-1); } } /* try loading the disk image */ if (roflag != 0) { ce->ro = 1; ce->fd = open(disk2fname(arg), O_RDONLY | O_BINARY); } else { ce->ro = 0; ce->fd = open(disk2fname(arg), O_RDWR | O_BINARY); } if (ce->fd == -1) { sprintf(msg, "ERROR: disk %s not found$", arg); return(-1); } /* good - how many sectors do we have? */ if (read(ce->fd, buff, sizeof(buff)) != sizeof(buff)) { sprintf(msg, "ERROR: disk %s malformed$", arg); close(ce->fd); ce->fd = -1; return(-1); } ce->sectcount = (unsigned short)((buff[0x14] << 8) | buff[0x13]); ce->lastimgsector = (unsigned short)(lseek(ce->fd, 0, SEEK_END) / 512); if ((ce->lastimgsector == 0) || ((ce->lastimgsector > ce->sectcount))) { sprintf(msg, "ERROR: disk %s malformed (lastimgsector=%u sectcount=%u)$", arg, ce->lastimgsector, ce->sectcount); close(ce->fd); ce->fd = -1; return(-1); } ce->lastimgsector -= 1; /* find out the type of floppy */ i = getFDPARMSidbysize(ce->sectcount / 2); if (i < 0) { close(ce->fd); ce->fd = -1; sprintf(msg, "ERROR: unknown disk format$"); return(-1); } ce->chs_sectspertrack = FDPARMS[i].secttrack; ce->chs_totheads = FDPARMS[i].heads; strncpy(ce->curflopid, arg, sizeof(ce->curflopid)); if (ce->driveid < 2) { /* newer ethflop provide drive id */ sprintf(msg, "Disk %s loaded (%d KiB%s) in drive %c:$", ce->curflopid, ce->sectcount / 2, (ce->ro == 0)?"":", write-protected", 'A' + ce->driveid); } else { sprintf(msg, "Disk %s loaded (%d KiB%s)$", ce->curflopid, ce->sectcount / 2, (ce->ro == 0)?"":", write-protected"); } ce->diskchangeflag = 1; return(0); } static int process_ctrl(struct FRAME *frame, struct cliententry *ce, char *log) { int argc; char cmd[8], arg[16], arg2[16]; const struct cliententry *sce; /* switch src and dst addresses so the reply header is ready */ memcpy(frame->dmac, frame->smac, 6); /* copy source mac into dst field */ memcpy(frame->smac, MYMAC, 6); /* copy my mac into source field */ /* parse command list */ argc = parseargs(cmd, sizeof(cmd) - 1, arg, arg2, sizeof(arg) - 1, (char *)frame->data); lostring(cmd, sizeof(cmd)); upstring(arg, sizeof(arg)); upstring(arg2, sizeof(arg2)); if (argc < 1) { sprintf(log, "illegal query from %s", printmac(ce->mac)); return(-1); } /* clear out pkt data */ memset(frame->data, '*', 512); /* SHOW STATUS */ if (strcmp(cmd, "s") == 0) { fill_status_msg(frame->data, ce); goto DONE; } /* INSERT (READ+WRITE) */ if (strcmp(cmd, "i") == 0) { ethflop_insert(ce, arg, (char *)(frame->data), 0); goto DONE; } /* INSERT (WRITE-PROTECTED) */ if (strcmp(cmd, "ip") == 0) { ethflop_insert(ce, arg, (char *)(frame->data), 1); goto DONE; } /* EJECT */ if (strcmp(cmd, "e") == 0) { ethflop_eject(ce, (char *)(frame->data)); goto DONE; } /* DELETE (REMOVE) */ if (strcmp(cmd, "d") == 0) { if (arg[0] == 0) { sprintf((char *)(frame->data), "ERROR: you must specify a diskname$"); goto DONE; } if (validatediskname(arg) != 0) { sprintf((char *)(frame->data), "ERROR: specified disk name is invalid$"); goto DONE; } sce = findcliententrybylock(arg); if (sce != NULL) { sprintf((char *)(frame->data), "ERROR: disk %s is currently being used by %s$", arg, printmac(sce->mac)); goto DONE; } /* try removing it */ if (unlink(disk2fname(arg)) != 0) { sprintf((char *)(frame->data), "ERROR: failed to delete disk %s (%s)$", arg, strerror(errno)); goto DONE; } sprintf((char *)(frame->data), "Disk %s has been deleted$", arg); goto DONE; } /* LISTING */ if (strcmp(cmd, "l") == 0) { char *ptr = (char *)(frame->data); if (ethflop_imgfilelist(ptr, sizeof(frame->data)) == 0) { sprintf(ptr, "no virtual floppy disks available$"); } goto DONE; } /* NEW IMAGE */ if (cmd[0] == 'n') { static unsigned char imghdr16k[16384]; unsigned short fsize_sectors; clock_t t1; int i; /* used to measure the time it takes to create the disk */ t1 = clock(); /* zero out image headerr to avoid leaving there any kind of garbage data */ memset(imghdr16k, 0, sizeof(imghdr16k)); /* compute the header */ fsize_sectors = floppygen(imghdr16k, atoi(cmd + 1)); if (fsize_sectors == 0) { char *ptr = (char *)(frame->data); int sz; sz = sprintf(ptr, "Valid floppy sizes are:\r\n"); for (i = 0; FDPARMS[i].md != 0; i++) { if (sz + strlen(FDPARMS[i].description) + 12 > 512) break; sz += sprintf(ptr + sz, "%5d: %s\r\n", FDPARMS[i].totsectors / 2, FDPARMS[i].description); } ptr[sz] = '$'; /* append the DOS string terminator */ goto DONE; } if (validatediskname(arg) != 0) { sprintf((char *)(frame->data), "ERROR: specified disk name is invalid$"); goto DONE; } if (fileexists(disk2fname(arg)) != 0) { sprintf((char *)(frame->data), "ERROR: a disk with this name already exists$"); goto DONE; } /* try creating the image, set i to errno on error */ i = img_create(disk2fname(arg), imghdr16k, fsize_sectors); if (i != 0) { sprintf((char *)(frame->data), "ERROR: disk %s failed to be initialized (%s)$", arg, strerror(i)); sprintf(log, "ERR: creating disk %s (%u sects) failed (%s)$", arg, fsize_sectors, strerror(i)); } else { sprintf((char *)(frame->data), "Disk %s created (%u KiB)$", arg, fsize_sectors / 2); /* measure time (and convert to ms) */ t1 = (clock() - t1) * 1000 / CLOCKS_PER_SEC; sprintf(log, "Disk %s created (%u KiB), generated in %lu ms", arg, fsize_sectors / 2, (unsigned long)t1); } goto DONE; } /* RENAME */ if (cmd[0] == 'r') { char buff[20]; /* validate src and dst disk names */ if ((validatediskname(arg) != 0) || (validatediskname(arg2) != 0)) { sprintf((char *)(frame->data), "ERROR: invalid disk name$"); goto DONE; } /* make sure src exists */ if (fileexists(disk2fname(arg)) == 0) { sprintf((char *)(frame->data), "ERROR: %s disk does not exist$", arg); goto DONE; } /* make sure dst does not exists yet */ if (fileexists(disk2fname(arg2)) != 0) { sprintf((char *)(frame->data), "ERROR: %s disk already exists$", arg2); goto DONE; } /* make sure src is not in use */ sce = findcliententrybylock(arg); if (sce != NULL) { sprintf((char *)(frame->data), "ERROR: the %s disk is currently used by %s$", arg, printmac(sce->mac)); goto DONE; } /* do it - but convert arg to fname first since disk2fname() cannot be * called twice as it uses a single static buffer */ strcpy(buff, disk2fname(arg)); if (rename(buff, disk2fname(arg2)) != 0) { sprintf((char *)(frame->data), "ERROR: %s$", strerror(errno)); goto DONE; } sprintf((char *)(frame->data), "Disk %s renamed to %s$", arg, arg2); goto DONE; } sprintf((char *)(frame->data), "invalid command$"); DONE: /* set answer to current flopid */ memcpy(&(frame->ax), ce->curflopid, 8); return(0); } /* used for debug output of frames on screen */ #if DEBUG > 0 static void dumpframe(const void *ptr, int len) { int i, b; int lines; const int LINEWIDTH=16; const unsigned char *frame = ptr; const struct FRAME *fields = ptr; char flopid[16]; /* FIELDS */ printf(" * ax=0x%04X bx=0x%04X cx=0x%04X dx=0x%04X\n", le16toh(fields->ax), le16toh(fields->bx), le16toh(fields->cx), le16toh(fields->dx)); memset(flopid, 0, sizeof(flopid)); memcpy(flopid, fields->flopid, 8); printf(" * flopid=%s reqid=%u sectnum=%u csum=0x%04X\n", flopid, fields->reqid, fields->sectnum, le16toh(fields->csum)); /* HEX DUMP NOW */ lines = (len + LINEWIDTH - 1) / LINEWIDTH; /* compute the number of lines */ /* display line by line */ for (i = 0; i < lines; i++) { /* read the line and output hex data */ for (b = 0; b < LINEWIDTH; b++) { int offset = (i * LINEWIDTH) + b; if (b == LINEWIDTH / 2) printf(" "); if (offset < len) { printf(" %02X", frame[offset]); } else { printf(" "); } } printf(" | "); /* delimiter between hex and ascii */ /* now output ascii data */ for (b = 0; b < LINEWIDTH; b++) { int offset = (i * LINEWIDTH) + b; if (b == LINEWIDTH / 2) printf(" "); if (offset >= len) { printf(" "); continue; } if ((frame[offset] >= ' ') && (frame[offset] <= '~')) { printf("%c", frame[offset]); } else { printf("."); } } /* newline and loop */ printf("\n"); } } #endif /* compute the eflop csum of frame, result is always little-endian */ static unsigned short cksum(const struct FRAME *frame) { unsigned short res = 0; const uint16_t *ptr = (const void *)&(frame->protover); int l = 10 + 256; /* how many words to process */ while (l--) { res = (unsigned short)(res >> 15) | (unsigned short)(res << 1); /* rol 1 */ res ^= le16toh(*ptr); ptr++; } return(htole16(res)); } void ethflop_init(const unsigned char *mymac) { memcpy(MYMAC, mymac, 6); glob_clist = NULL; } const struct FRAME *ethflop_process(const void *frameptr, unsigned short framelen, char *log) { struct cliententry *ce; const struct FRAME *frame = frameptr; struct FRAME *ceframe = NULL; unsigned short cksum_mine; /* validate frame length */ if (framelen != sizeof(struct FRAME)) return(NULL); /* validate this is for me (or broadcast) */ if ((memcmp(MYMAC, frame->dmac, 6) != 0) && (memcmp("\xff\xff\xff\xff\xff\xff", frame->dmac, 6) != 0)) return(NULL); /* skip anything that is not for me */ /* is this valid ethertype ?*/ if ((frame->etype != htobe16(0xEFDD)) && (frame->etype != htobe16(0xEFDC))) { sprintf(log, "Error: Received invalid ethertype frame"); return(NULL); } /* validate CKSUM */ cksum_mine = cksum(frame); if (cksum_mine != frame->csum) { sprintf(log, "CHECKSUM MISMATCH! Computed: 0x%02Xh Received: 0x%02Xh", cksum_mine, frame->csum); return(NULL); } #if DEBUG > 0 DBG("Received frame from %s\n", printmac(frame->smac)); dumpframe(frame, sizeof(struct FRAME)); #endif #if SIMLOSS_INP > 0 /* simulated frame LOSS (input) */ if ((rand() % 100) < SIMLOSS_INP) { sprintf(log, "INPUT LOSS! (reqid %u)", frame->reqid); return(NULL); } #endif /* find client entry */ ce = findorcreateclient(frame->smac, log); if (ce == NULL) { sprintf(log, "ERROR: OUT OF MEMORY!"); return(NULL); } /* is it a retransmission that I processed already? */ if ((ce->last_frame_recvd_reqid == frame->reqid) && (ce->last_frame_recvd_csum == frame->csum)) { sprintf(log, "retransmission, query 0x%02X answered from cache", frame->reqid); return(&(ce->last_frame_sent)); } /* reset csum and reqid to avoid false cache hits in case of an error */ ce->last_frame_recvd_csum = 0; ce->last_frame_recvd_reqid = 0; /* copy frame to client context */ ceframe = &(ce->last_frame_sent); memcpy(ceframe, frame, sizeof(struct FRAME)); /* convert ax/bx/cx/dx to host order */ ceframe->ax = le16toh(ceframe->ax); ceframe->bx = le16toh(ceframe->bx); ceframe->cx = le16toh(ceframe->cx); ceframe->dx = le16toh(ceframe->dx); /* process frame */ if (frame->etype == htobe16(0xEFDD)) { if (process_data(ceframe, ce, log) != 0) return(NULL); } else if (frame->etype == htobe16(0xEFDC)) { if (process_ctrl(ceframe, ce, log) != 0) return(NULL); } else { sprintf(log, "Error: unsupported ethertype from %s", printmac(frame->smac)); return(NULL); } #if SIMLOSS_OUT > 0 /* simulated frame LOSS (output) */ if ((rand() % 100) < SIMLOSS_OUT) { sprintf(log, "OUTPUT LOSS! (reqid %u)", frame->reqid); return(NULL); } #endif DBG("---------------------------------\n"); /* convert new register values to little-endian */ ceframe->ax = htole16(ceframe->ax); ceframe->bx = htole16(ceframe->bx); ceframe->cx = htole16(ceframe->cx); ceframe->dx = htole16(ceframe->dx); /* fill in checksum into the answer */ ceframe->csum = cksum(ceframe); /* remember what answer has been answered so I can cache it */ ce->last_frame_recvd_csum = frame->csum; ce->last_frame_recvd_reqid = frame->reqid; #if DEBUG > 0 DBG("Sending back an answer of %lu bytes\n", sizeof(struct FRAME)); dumpframe(ceframe, sizeof(struct FRAME)); #endif return(ceframe); } struct cliententry *ethflop_getclients(void) { return(glob_clist); } ./PaxHeaders/test.c0000644000000000000000000000013214672035463011426 xustar0030 mtime=1726495539.327401489 30 atime=1726495547.035593727 30 ctime=1726495539.327401489 test.c0000644000175000001440000000315714672035463013130 0ustar00mateuszusers00000000000000/* * basic test suite for ethflop/ethflopd * Copyright (C) 2019 Mateusz Viste * * to be compiled with Turbo C 2.01 */ #include /* farmalloc(), farfree() */ #include #include #include int main(int argc, char **argv) { union REGS r; struct SREGS sr; unsigned short TESTFLAG = 0; unsigned char far *sectorbuff; int i; if (argc == 1) { TESTFLAG = 0xffffu; } else { for (i = 1; i < argc; i++) { printf("enabling test #%d\r\n", atoi(argv[i])); TESTFLAG |= atoi(argv[i]); } } sectorbuff = farmalloc(512); if (sectorbuff == NULL) { printf("out of memory\n"); return(1); } if (TESTFLAG & 1) { printf("TEST 1: DISK RESET... "); r.h.ah = 0; /* disk reset */ r.h.dl = 0; /* A: */ int86(0x13, &r, &r); printf("CF=%d AH=0x%02X\r\n", r.x.cflag, r.h.ah); if (r.x.cflag != 0) goto GAMEOVER; } if (TESTFLAG & 2) { printf("TEST 2: READ SECT 0 x1000 times... "); for (i = 0; i < 1000; i++) { r.h.ah = 2; /* read sector */ r.h.al = 1; /* one sector */ r.h.ch = 0; /* cylinder 0 */ r.h.cl = 1; /* sector 1 (first, sectors starts at 1...) */ r.h.dh = 0; /* head 0 */ r.h.dl = 0; /* A: */ sr.es = FP_SEG(sectorbuff); r.x.bx = FP_OFF(sectorbuff); int86x(0x13, &r, &r, &sr); if (r.x.cflag != 0) break; } printf("CF=%d AH=0x%02X (done %d times)\r\n", r.x.cflag, r.h.ah, i); if (r.x.cflag != 0) goto GAMEOVER; } printf("ALL GOOD\r\n"); GAMEOVER: farfree(sectorbuff); return(0); } ./PaxHeaders/ui_dos.c0000644000000000000000000000013214672035463011731 xustar0030 mtime=1726495539.327401489 30 atime=1726495547.035593727 30 ctime=1726495539.327401489 ui_dos.c0000644000175000001440000003244314672035463013433 0ustar00mateuszusers00000000000000/* * ETHFLOP SERVER FOR DOS * * http://ethflop.sourceforge.net * * Copyright (C) 2019-2024 Mateusz Viste * MIT license */ #include "stdio.h" #include #include "mdr\bios.h" #include "mdr\dos.h" #include "mdr\cout.h" #include "mdr\pktdrv.h" /* core ethflop server logic */ #include "core.h" #include "version.h" /* program version */ struct PKTFRAME { unsigned short len; unsigned char payload[1]; }; /* writes into buff the human (ASCIZ) representation of mac. buff must be * at least 18 bytes long */ static void mactostring(char *buff, const unsigned char *mac) { sprintf(buff, "%02X:%02X:%02X:%02X:%02X:%02X", mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]); } static int init_pkt_ethflop(unsigned short *handles, unsigned char *pktint, unsigned char *mymac) { char buff[32]; *pktint = mdr_pktdrv_init(0); if (*pktint == 0) { mdr_coutraw_puts("ERROR: packet driver not found"); return(-1); } mdr_coutraw_str("Packet driver found at int 0x"); sprintf(buff, "%02X", *pktint); mdr_coutraw_puts(buff); handles[0] = mdr_pktdrv_accesstype(0xEFDC); handles[1] = mdr_pktdrv_accesstype(0xEFDD); if ((handles[0] == 0xffff) || (handles[1] == 0xffff)) { mdr_coutraw_puts("ERROR: Packet driver initialization (accesstype) failed"); return(-2); } mdr_coutraw_str("My MAC address is "); mdr_pktdrv_getmac(mymac, handles[0]); mactostring(buff, mymac); mdr_coutraw_puts(buff); ethflop_init(mymac); return(0); } /* returns the amount of clients connected */ static unsigned char display_clients_mac(unsigned char maxlines) { char buff[20]; unsigned char line; struct cliententry *ce = ethflop_getclients(); unsigned char count = 0; for (line = 1; line < maxlines; line++) { mdr_cout_char(line, 0, ' ', 0x07); if (ce == NULL) { if (line == 1) { mdr_cout_str(line, 1, " ", 0x07, 18); } else { mdr_cout_str(line, 1, " ", 0x07, 18); } } else { count++; mactostring(buff, ce->mac); buff[17] = ' '; mdr_cout_str(line, 1, buff, 0x07, 18); } if (ce != NULL) ce = ce->next; } return(count); } static struct cliententry *display_clients_flops(unsigned char maxlines, unsigned char selected_client) { unsigned char line; unsigned char attr; struct cliententry *ce = ethflop_getclients(); struct cliententry *res = NULL; if (ce == NULL) return(NULL); for (line = 1; line < maxlines; line++) { if (line == selected_client + 1) { attr = 0x70; res = ce; } else { attr = 0x07; } if (ce == NULL) { mdr_cout_str(line, 19, " ", attr, 15); } else if (ce->curflopid[0] == 0) { mdr_cout_str(line, 19, " < NO FLOPPY > ", attr, 15); } else { unsigned char l; mdr_cout_char(line, 19, ' ', attr); l = mdr_cout_str(line, 20, ce->curflopid, attr, 8); mdr_cout_char_rep(line, 20 + l, ' ', attr, 9 - l); if (ce->ro != 0) { mdr_cout_str(line, 29, "[RO] ", attr, 5); } else { mdr_cout_str(line, 29, " ", attr, 5); } } if (ce != NULL) ce = ce->next; } return(res); } static void ui_initialscreen(unsigned char screen_w, unsigned char screen_h) { unsigned char i; mdr_cout_cls(0x07); mdr_cout_str(0, 1, "Connected clients:", 0x07, 0xff); mdr_cout_str(screen_h - 5, 0, "=== DEBUG LOGS", 0x07, 0xff); mdr_cout_char_rep(screen_h - 5, 15, '=', 0x07, screen_w - 15); for (i = 0; i < screen_h - 5; i++) { mdr_cout_char(i, 34, '|', 0x07); } mdr_cout_str(0, 36, "ETHFLOP SERVER for DOS ver " PVER, 0x07, 0xff); mdr_cout_str(2, 36, "E = Eject floppy ENTER = Insert new floppy", 0x07, 0xff); mdr_cout_str(3, 36, "ESC = QUIT", 0x07, 0xff); mdr_cout_str(5, 36, "Available floppy image files:", 0x07, 0xff); } static void logmsg(char *msg, char screen_w, char screen_h) { unsigned char l; if (msg[0] == 0) return; /* move the current 4 log lines up by 1 line */ mdr_cout_scrollup(0x07, screen_h - 4, 0, screen_h - 1, screen_w - 1, 1); /* print the new log */ l = mdr_cout_str(screen_h - 1, 0, msg, 0x07, 80); mdr_cout_char_rep(screen_h - 1, l, ' ', 0x07, 80 - l); /* reset the log string */ msg[0] = 0; } static const char *ui_refresh_imglist(char *bigbuff, unsigned short bigbuffsz, unsigned char *selected) { unsigned short imgcount; unsigned short i; unsigned char attr; static unsigned short imgcount_last; const char *res = NULL; imgcount = ethflop_imgfilelist(bigbuff, bigbuffsz); if (imgcount == 0) { bigbuff[0] = 0; /* mark the list as empty (UI needs this) */ } if ((selected != NULL) && (*selected >= imgcount) & (imgcount > 0)) *selected = imgcount - 1; if (imgcount > 56) { memcpy(bigbuff + 550, "...", 4); imgcount = 56; } for (i = 0; i < imgcount; i++) { unsigned char len; if ((selected != NULL) && (i == *selected)) { attr = 0x70; res = bigbuff + i * 10; } else { attr = 0x07; } len = mdr_cout_str(6 + (i / 4), 36 + (i & 3) * 10, bigbuff + i * 10, attr, 8); if (len < 8) { mdr_cout_char_rep(6 + (i / 4), 36 + len + (i & 3) * 10, ' ', 0x07, 8 - len); } } /* print as many empty entries as necessary to cover up entries that were * displayed last time and no longer present */ for (;i < imgcount_last; i++) { mdr_cout_char_rep(6 + (i / 4), 36 + (i & 3) * 10, ' ', 0x07, 8); } imgcount_last = imgcount; return(res); } static void idle(void); #pragma aux idle = \ "mov ax, 0x1680" \ "int 0x2F" \ "test al, al" /* AL is zeroed if 1680h (release cycle) suceeded */ \ "jz DONE" \ "sti" /* otherwise do a hlt myself */ \ "hlt" \ "DONE:" \ modify [ax]; #define FLG_QUIT 128 #define FLG_NOUI 64 #define FLG_HLT 32 /* parses command line and returns a set of FLG_* flags */ static unsigned char parse_cmdline(int argc, char **argv) { unsigned char i; unsigned char flags = 0; for (i = 1; i < argc; i++) { if (strcasecmp(argv[i], "/noui") == 0) { flags |= FLG_NOUI; } else if (strcasecmp(argv[i], "/hlt") == 0) { flags |= FLG_HLT; } else { /* HELP SCREEN */ mdr_coutraw_puts("ETHFLOPD ver " PVER " (C) " PDATE " Mateusz Viste"); mdr_coutraw_crlf(); mdr_coutraw_puts("usage: ethflopd [options]"); mdr_coutraw_crlf(); mdr_coutraw_puts("options:"); mdr_coutraw_crlf(); mdr_coutraw_puts("/noui disable the user interface"); mdr_coutraw_puts("/hlt enable power saving (might cause bad performances)"); mdr_coutraw_crlf(); mdr_coutraw_puts("MIT license. http://ethflop.sourceforge.net"); return(FLG_QUIT); } } return(flags); } int main(int argc, char **argv) { unsigned char pktint; unsigned char mymac[6]; unsigned short pkt_handles[2] = {0xffff, 0xffff}; /* the two handles obtained from the pkt driver */ struct PKTFRAME *frame; const struct FRAME *answer; static unsigned char ui_refresh; /* what element of UI to refresh */ static unsigned char selected_client; static unsigned char exitflag; static unsigned char screen_w, screen_h, screen_c; static unsigned short last_ui_time; static struct cliententry *selected_client_ptr; static char log[128]; static char imglistbuff[600]; /* must hold 56 floppy names */ static const char *imglistbuffptr; static unsigned char clientscount; static unsigned char selectionmode; /* 0=clients 1=images */ static unsigned char selectedimage; /* selected floppy image (when selectionmode=1) */ static unsigned char flags; flags = parse_cmdline(argc, argv); if (flags & FLG_QUIT) return(1); mdr_coutraw_crlf(); mdr_coutraw_puts("ETHFLOP server for DOS ver " PVER " starting..."); mdr_coutraw_crlf(); if (init_pkt_ethflop(pkt_handles, &pktint, mymac) != 0) return(2); frame = mdr_pktdrv_getbufptr(); sprintf(log, "Ready, waiting for clients..."); /*** simplified (no UI) version ********************************************/ if (flags & FLG_NOUI) { mdr_coutraw_puts("[PRESS ANY KEY TO QUIT]"); for (;;) { /* log pending? */ if (log[0] != 0) { mdr_coutraw_puts(log); log[0] = 0; } if ((frame->len == 0) || (frame->len & 0x8000)) { if (mdr_dos_keypending() != 0) { mdr_coutraw_puts("Aborted by user"); mdr_dos_flushkeyb(); break; } if (flags & FLG_HLT) idle(); continue; } answer = ethflop_process(frame->payload, frame->len, log); /* processing done - mark the input pktdrv buffer as empty */ frame->len = 0; /* send the answer (if any) */ if (answer != NULL) { mdr_pktdrv_send(answer, sizeof(struct FRAME)); } } goto GOODBYE_NOUI; } /*** full user interface version *******************************************/ screen_c = mdr_cout_init(&screen_w, &screen_h); ui_initialscreen(screen_w, screen_h); mdr_cout_cursor_hide(); while (exitflag == 0) { /* display pending log, if any */ logmsg(log, screen_w, screen_h); /* wait for a request... */ /* if nothing received then loop for keyboard actions and signal IDLE time */ if ((frame->len == 0) || (frame->len & 0x8000) || (last_ui_time != mdr_bios_ticks() & 0xf0)) { /* force a UI check at least once a second */ last_ui_time = mdr_bios_ticks() & 0xf0; switch (ui_refresh & 7) { case 0: /* keyb polling */ if (mdr_dos_keypending() != 0) { unsigned short k = mdr_dos_getkey2(); switch (k) { case 0x1B: /* ESC */ if (selectionmode != 0) { selectionmode = 0; } else { exitflag = 1; } break; case 0x148: /* up */ if (selectionmode == 0) { if (selected_client > 0) selected_client--; } else { if (selectedimage > 4) { selectedimage -= 4; } else { selectedimage = 0; } } break; case 0x150: /* down */ if (selectionmode == 0) { if ((selected_client < screen_h - 7) && (selected_client + 1 < clientscount)) selected_client++; } else { if (selectedimage <= 52) selectedimage += 4; } break; case 0x14B: /* left */ if ((selectionmode != 0) && (selectedimage > 0)) selectedimage--; break; case 0x14D: /* right */ if ((selectionmode != 0) && (selectedimage < 56)) selectedimage++; break; case 'e': if ((selectionmode == 0) && (selected_client_ptr != NULL)) ethflop_eject(selected_client_ptr, log); break; case '\r': if (selectionmode != 0) { if ((imglistbuffptr != NULL) && (selected_client_ptr != NULL)) { /* eject floppy if present */ if (selected_client_ptr->curflopid[0] != 0) { ethflop_eject(selected_client_ptr, log); logmsg(log, screen_w, screen_h); } /* insert new floppy */ ethflop_insert(selected_client_ptr, imglistbuffptr, log, 0); } selectionmode = 0; } else { if (imglistbuff[0] == 0) { sprintf(log, "No floppy images available (create some using ethflop)"); } else if (selected_client_ptr != NULL) { selectionmode = 1; } } break; } } break; case 1: /* list of clients (MAC) */ clientscount = display_clients_mac(screen_h - 5); break; case 2: /* list of clients (floppies names) */ if (selectionmode == 0) { selected_client_ptr = display_clients_flops(screen_h - 5, selected_client); } else { display_clients_flops(screen_h - 5, 0xff); } break; case 3: /* if *really* idle then try to do some power saving or refresh the list of available floppy images */ if (frame->len == 0) { static unsigned char imglist; if (imglist++ & 0x03) { if (flags & FLG_HLT) idle(); } else { /* refresh list of floppy images */ if (selectionmode != 0) { imglistbuffptr = ui_refresh_imglist(imglistbuff, sizeof(imglistbuff), &selectedimage); } else { ui_refresh_imglist(imglistbuff, sizeof(imglistbuff), NULL); } } } break; } ui_refresh++; continue; } /* ask ethflop core for the answer frame */ answer = ethflop_process(frame->payload, frame->len, log); frame->len = 0; /* send the answer (if any) */ if (answer != NULL) { mdr_pktdrv_send(answer, sizeof(struct FRAME)); } } mdr_cout_close(); mdr_coutraw_puts("ETHFLOPD ver " PVER " (C) " PDATE " Mateusz Viste"); GOODBYE_NOUI: mdr_coutraw_puts("Goodbye"); if (pkt_handles[0] != 0xffff) mdr_pktdrv_free(pkt_handles[0]); if (pkt_handles[1] != 0xffff) mdr_pktdrv_free(pkt_handles[1]); return(0); } ./PaxHeaders/ui_posix.c0000644000000000000000000000013214672035463012306 xustar0030 mtime=1726495539.327401489 30 atime=1726495547.035593727 30 ctime=1726495539.327401489 ui_posix.c0000644000175000001440000004344614672035463014015 0ustar00mateuszusers00000000000000/* * this file is part of ethflopd, it provides the ethernet backend for Linux * Copyright (C) 2019-2024 Mateusz Viste */ #include /* htons() */ #include #include #include #include #include #include /* stderr, fprintf()... */ #include /* NULL etc */ #include /* memset(), memcpy()... */ #include #include #include /* unix socket */ #include #include /* time() */ #include /* close() */ #include "core.h" /* ethflop_process() */ #include "version.h" /* program version */ /* UDP port for control queries */ #define UDP_PORT 1390 static int datasock = -1; static int ctrlsock = -1; static int udpsock = -1; /* the flag is set when ethflopd is expected to terminate */ static sig_atomic_t volatile terminationflag = 0; static void sigcatcher(int sig) { switch (sig) { case SIGTERM: case SIGQUIT: case SIGINT: terminationflag = 1; break; default: break; } } /* opens the UDP control socket (for API queries, UDP/1390) */ static int open_udp_sock(void) { int sock; struct sockaddr_in sin; sock = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP); if (sock == -1) { syslog(LOG_ERR, "ERROR: failed to init UDP socket (%s)", strerror(errno)); fprintf(stderr, "ERROR: failed to init UDP socket (%s)\n", strerror(errno)); return(-1); } /* prep sock struct */ memset(&sin, 0, sizeof(sin)); sin.sin_family = AF_INET; sin.sin_port = htons(UDP_PORT); sin.sin_addr.s_addr = htonl(INADDR_LOOPBACK); /* bind */ if (bind(sock, (struct sockaddr *)&sin, sizeof(sin)) == -1) { syslog(LOG_ERR, "ERROR: failed to bind UDP/%u socket (%s)", UDP_PORT, strerror(errno)); fprintf(stderr, "ERROR: failed to bind UDP/%u socket (%s)\n", UDP_PORT, strerror(errno)); close(sock); return(-1); } return(sock); } static int raw_sock(const int protocol, const char *const interface, void *const hwaddr) { struct ifreq iface; struct sockaddr_ll addr; int socketfd, result; int ifindex; if ((interface == NULL) || (*interface == 0)) { errno = EINVAL; return(-1); } socketfd = socket(AF_PACKET, SOCK_RAW, htons(protocol)); if (socketfd == -1) return(-1); do { memset(&iface, 0, sizeof iface); strncpy((char *)&iface.ifr_name, interface, IFNAMSIZ); result = ioctl(socketfd, SIOCGIFINDEX, &iface); if (result == -1) break; ifindex = iface.ifr_ifindex; memset(&iface, 0, sizeof(iface)); strncpy((char *)&iface.ifr_name, interface, IFNAMSIZ); result = ioctl(socketfd, SIOCGIFFLAGS, &iface); if (result == -1) break; iface.ifr_flags |= IFF_PROMISC; result = ioctl(socketfd, SIOCSIFFLAGS, &iface); if (result == -1) break; memset(&iface, 0, sizeof iface); strncpy((char *)&iface.ifr_name, interface, IFNAMSIZ); result = ioctl(socketfd, SIOCGIFHWADDR, &iface); if (result == -1) break; memset(&addr, 0, sizeof addr); addr.sll_family = AF_PACKET; addr.sll_protocol = htons(protocol); addr.sll_ifindex = ifindex; addr.sll_hatype = 0; addr.sll_pkttype = PACKET_HOST | PACKET_BROADCAST; addr.sll_halen = ETH_ALEN; /* Assume ethernet! */ memcpy(&addr.sll_addr, &iface.ifr_hwaddr.sa_data, addr.sll_halen); if (hwaddr != NULL) memcpy(hwaddr, &iface.ifr_hwaddr.sa_data, ETH_ALEN); if (bind(socketfd, (struct sockaddr *)&addr, sizeof addr)) break; errno = 0; return(socketfd); } while (0); { const int saved_errno = errno; close(socketfd); errno = saved_errno; return(-1); } } static void eth_close(void) { if (datasock != -1) close(datasock); if (ctrlsock != -1) close(ctrlsock); if (udpsock != -1) close(udpsock); } static int eth_init(unsigned char *mymac, const char *ifname) { datasock = raw_sock(0xEFDD, ifname, mymac); if (datasock == -1) { syslog(LOG_ERR, "Error: failed to open socket (%s)", strerror(errno)); fprintf(stderr, "Error: failed to open socket (%s)\n" "\n" "Usually ethflopd requires to be launched as root for\n" "handling raw (ethernet) sockets. Are you root?\n", strerror(errno)); eth_close(); return(-1); } ctrlsock = raw_sock(0xEFDC, ifname, mymac); if (ctrlsock == -1) { syslog(LOG_ERR, "Error: failed to open socket (%s)", strerror(errno)); fprintf(stderr, "Error: failed to open socket (%s)\n" "\n" "Usually ethflopd requires to be launched as root for\n" "handling raw (ethernet) sockets. Are you root?\n", strerror(errno)); eth_close(); return(-1); } return(0); } static unsigned short eth_recv(void *frame, size_t framesz, struct sockaddr_in **udpclient) { static struct sockaddr_in sin; socklen_t sinlen = sizeof(struct sockaddr_in); fd_set fdset; int highestfd; int len; struct timeval stimeout = {2, 0}; /* set timeout to 2s */ highestfd = datasock; if (ctrlsock > highestfd) highestfd = ctrlsock; if (udpsock > highestfd) highestfd = udpsock; /* prepare the set of descriptors to be monitored later through select() */ FD_ZERO(&fdset); FD_SET(datasock, &fdset); FD_SET(ctrlsock, &fdset); FD_SET(udpsock, &fdset); *udpclient = NULL; /* wait for something to happen on my socket */ if (select(highestfd + 1, &fdset, NULL, NULL, &stimeout) < 1) { return(0); } if (FD_ISSET(datasock, &fdset)) { len = recv(datasock, frame, framesz, MSG_DONTWAIT); if (len > 0) return((unsigned short)len); } if (FD_ISSET(ctrlsock, &fdset)) { len = recv(ctrlsock, frame, framesz, MSG_DONTWAIT); if (len > 0) return((unsigned short)len); } if (FD_ISSET(udpsock, &fdset)) { len = recvfrom(udpsock, frame, framesz, 0, (struct sockaddr *)&sin, &sinlen); if (len > 0) { *udpclient = &sin; return((unsigned short)len); } } return(0); } static int eth_send(const void *frame, unsigned short framelen) { int len; len = (int)send(datasock, frame, framelen, 0); if (len < 0) { syslog(LOG_ERR, "ERROR: send() returned %d (%s)", len, strerror(errno)); } else if (len != framelen) { syslog(LOG_ERR, "ERROR: send() sent less than expected (%d != %lu)", len, sizeof(frame)); } else { return(0); } return(-1); } /* daemonize the process, return 0 on success, non-zero otherwise */ static int daemonize(void) { pid_t mypid; /* I don't want to get notified about SIGHUP */ signal(SIGHUP, SIG_IGN); /* fork off */ mypid = fork(); if (mypid == 0) { /* I'm the child, do nothing */ /* nothing to do - just continue */ } else if (mypid > 0) { /* I'm the parent - quit now */ exit(0); } else { /* error condition */ return(-2); } return(0); } static void help(void) { puts("ethflopd version " PVER " | Copyright (C) " PDATE " Mateusz Viste\n" "http://ethflop.sourceforge.net\n" "\n" "usage: ethflopd [options] interface storagedir\n" " ethflopd -c control query\n" "\n" "Options:\n" " -f Keep in foreground (do not daemonize)\n" " -h Display this information\n" "\n" "The '-c' mode is for sending control commands to ethflopd on behalf of\n" "an ethflop client. Possible queries:\n" "\n" "imglist returns the list of available floppy images\n" "clilist returns the list of clients\n" "e MAC eject floppy used by client MAC (format 00:11:22:33:44:55:66)\n" "i MAC DNAME insert floppy image DNAME to client MAC\n" "I MAC DNAME same as 'i' but inserts the floppy img write-protected\n" ); } static int udpclient(int argcount, char * const *args) { struct sockaddr_in addr; int fd; int i; char buff[1024]; if (argcount == 0) { fprintf(stderr, "bad syntax, go read 'ethflopd --help' again.\n"); return(-1); } fd = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP); if (fd == -1) { fprintf(stderr, "ERROR: failed to open UDP socket (%s)\n", strerror(errno)); return(-1); } /* prep sock struct */ memset(&addr, 0, sizeof(addr)); addr.sin_family = AF_INET; addr.sin_port = htons(UDP_PORT); addr.sin_addr.s_addr = htonl(INADDR_LOOPBACK); buff[0] = 0; for (i = 0; i < argcount; i++) { if (i > 0) strcat(buff, " "); if (strlen(buff) + strlen(args[i]) + 2 > sizeof(buff)) { fprintf(stderr, "commandline too long\n"); close(fd); return(-1); } strcat(buff, args[i]); } if (sendto(fd, buff, strlen(buff), MSG_NOSIGNAL, (struct sockaddr *)&addr, sizeof(addr)) == -1) { fprintf(stderr, "ERROR: send() to control sock failed (%s)\n", strerror(errno)); close(fd); return(-1); } /* wait for an answer */ for (i = 0; i < 2; i++) { int len; fd_set set; struct timeval stimeout = {1, 0}; /* set timeout to 1s */ /* prepare the set of descriptors to be monitored later through select() */ FD_ZERO(&set); FD_SET(fd, &set); /* wait for something to happen on my socket */ if (select(fd + 1, &set, NULL, NULL, &stimeout) < 1) continue; len = recv(fd, buff, sizeof(buff), 0); for (i = 0; i < len; i++) { if (buff[i] == '\r') continue; if (buff[i] == '$') continue; printf("%c", buff[i]); } puts(""); break; } close(fd); /* timeout? */ if (i == 2) { fprintf(stderr, "timeout\n"); return(-1); } return(0); } /* converts a MAC string "AA:BB:CC:DD:EE:FF" into binary (6 bytes), returns * zero on success, non-zero on MAC parsing error */ static int macstr2bin(unsigned char *bin, const char *s) { int i, digcount; for (i = 0; i < 16;) { digcount = 0; NEXTDIGIT: *bin <<= 4; if ((s[i] >= '0') && (s[i] <= '9')) { *bin |= (s[i] - '0'); } else if ((s[i] >= 'A') && (s[i] <= 'F')) { *bin |= (s[i] + 10 - 'A'); } else if ((s[i] >= 'a') && (s[i] <= 'f')) { *bin |= (s[i] + 10 - 'a'); } else { /* bad format (not hex) */ return(-1); } i++; if (digcount++ == 0) goto NEXTDIGIT; i++; /* skip colon (:) */ bin++; } return(0); } static struct cliententry *getclient_by_mac(const unsigned char *mac) { struct cliententry *clist = ethflop_getclients(); while (clist != NULL) { if (memcmp(clist->mac, mac, 6) == 0) return(clist); clist = clist->next; } return(NULL); } /* processes an API (UDP) command of len bytes in buff. writes the answer to buff, up to buffsz bytes. returns the length of the returned answer */ static unsigned short process_udp_query(char *buff, unsigned short len, size_t buffsz) { /* terminate buff with a NUL for easier parsing */ buff[len] = 0; /* list images (imglist) */ if (strcasecmp(buff, "imglist") == 0) { unsigned short l = 0; char list[1024]; unsigned short imgcount, i; imgcount = ethflop_imgfilelist(list, sizeof(list)); l += sprintf(buff+l, "{\"images\": [\n"); for (i = 0; i < imgcount; i++) { if ((buffsz - l) < 20) break; if (i > 0) { l += sprintf(buff+l, ","); if ((i & 7) == 0) l += sprintf(buff+l, "\n"); } l += sprintf(buff+l, " \"%s\"", list + (i * 10)); } l += sprintf(buff+l, "\n]}"); return(l); } /* list clients (clilist) */ if (strcasecmp(buff, "clilist") == 0) { unsigned short l; struct cliententry *clist = ethflop_getclients(); l = sprintf(buff, "{ \"clients\": ["); while (clist != NULL) { if ((buffsz - l) < 128) break; if (l > 20) l += sprintf(buff+l, ","); /* mac */ l += sprintf(buff+l, "\n{\"mac\": \"%02X:%02X:%02X:%02X:%02X:%02X\", ", clist->mac[0], clist->mac[1], clist->mac[2], clist->mac[3], clist->mac[4], clist->mac[5]); /* img, roflag and sectcount */ l += sprintf(buff+l, "\"img\": \"%s\", \"ro\": %u, \"sectcount\": %u}", clist->curflopid, clist->ro, clist->sectcount); clist = clist->next; } l += sprintf(buff+l, "\n]}"); return(l); } /* eject */ if ((buff[0] == 'e') && (buff[1] == ' ')) { unsigned char mac[6]; char msg[256]; struct cliententry *ce; if (macstr2bin(mac, buff + 2) != 0) { return(sprintf(buff, "{\"errmsg\": \"bad MAC\"}")); } ce = getclient_by_mac(mac); if (ce == NULL) return(sprintf(buff, "{\"errmsg\": \"client not found\"}")); if (ethflop_eject(ce, msg) == 0) { return(sprintf(buff, "{\"msg\": \"%s\"}", msg)); } else { return(sprintf(buff, "{\"errmsg\": \"%s\"}", msg)); } return(strlen(buff)); } /* insert */ if (((buff[0] == 'i') && (buff[1] == ' ')) || ((buff[0] == 'I') && (buff[1] == ' '))) { unsigned char mac[6]; int i; struct cliententry *ce; unsigned char roflag = 0; char msg[256]; if (buff[0] == 'I') roflag = 1; if (macstr2bin(mac, buff + 2) != 0) { return(sprintf(buff, "{\"errmsg\": \"bad MAC\"}")); } /* skip MAC to find next argument */ for (i = 2; ; i++) { if (buff[i] == ' ') break; if (buff[i] == 0) return(sprintf(buff, "{\"errmsg\": \"missing disk name argument\"}")); } /* skip all spaces */ for (;; i++) { if (buff[i] == 0) return(sprintf(buff, "{\"errmsg\": \"missing disk name argument\"}")); if (buff[i] != ' ') break; } ce = getclient_by_mac(mac); if (ce == NULL) return(sprintf(buff, "{\"errmsg\": \"client not found\"}")); if (ethflop_insert(ce, buff + i, msg, roflag) == 0) { return(sprintf(buff, "{\"msg\": \"%s\"}", msg)); } else { return(sprintf(buff, "{\"errmsg\": \"%s\"}", msg)); } } /* else it is an unrecognized command */ return(sprintf(buff, "{\"errmsg\": \"unrecognized command\"}")); } int main(int argc, char **argv) { unsigned char mymac[6]; unsigned char frame[1024]; char logmsg[128]; const char *intname; const char *storagedir; int opt; int daemon = 1; /* daemonize self by default */ struct timespec tp1, tp2; /* used for calculating response time */ logmsg[0] = 0; /* client mode is a very special case */ if ((argc > 1) && (strcmp(argv[1], "-c") == 0)) { return(udpclient(argc - 2, argv + 2)); } while ((opt = getopt(argc, argv, "fh")) != -1) { switch (opt) { case 'f': /* -f: no daemon */ daemon = 0; break; case 'h': /* -h: help */ help(); return(0); case '?': /* error */ help(); return(1); } } /* I expect exactly two positional arguments */ if ((argc - optind) != 2) { help(); return(1); } intname = argv[optind++]; storagedir = argv[optind++]; /* setup signals catcher */ signal(SIGTERM, sigcatcher); signal(SIGQUIT, sigcatcher); signal(SIGINT, sigcatcher); /* switch to storage dir (that's where *.IMG files are) */ if (chdir(storagedir) != 0) { fprintf(stderr, "chdir() to '%s' failed\n", storagedir); syslog(LOG_ERR, "chdir() to '%s' failed", storagedir); return(1); } udpsock = open_udp_sock(); if (udpsock == -1) return(1); if (eth_init(mymac, intname) != 0) { fprintf(stderr, "eth_init() failed\n"); syslog(LOG_ERR, "eth_init() failed"); return(1); } if (daemon != 0) { if (daemonize() != 0) { fprintf(stderr, "Error: failed to daemonize!\n"); syslog(LOG_ERR, "Error: failed to daemonize!"); eth_close(); return(1); } } puts("ethflopd ver " PVER " started"); syslog(LOG_NOTICE, "ethflopd ver " PVER " started"); printf("Listening on '%s' [%02X:%02X:%02X:%02X:%02X:%02X] ; storage dir=%s ; ctrl socket at UDP/%u\n", intname, mymac[0], mymac[1], mymac[2], mymac[3], mymac[4], mymac[5], storagedir, UDP_PORT); ethflop_init(mymac); /* main loop */ while (terminationflag == 0) { const struct FRAME *answer; unsigned short framelen; struct sockaddr_in *udpclient; /* any log in buffer? output it to syslog and reset the buffer */ if (logmsg[0] != 0) { syslog(LOG_NOTICE, "%s", logmsg); logmsg[0] = 0; } framelen = eth_recv(&frame, sizeof(frame), &udpclient); if (framelen == 0) continue; /* is this from the UDP socket? */ if (udpclient != NULL) { framelen = process_udp_query((char *)(&frame), framelen, sizeof(frame)); if (sendto(udpsock, frame, framelen, 0, (struct sockaddr *)udpclient, sizeof(struct sockaddr_in)) == -1) { syslog(LOG_WARNING, "sendto() failed on UDP sock (%s)", strerror(errno)); fprintf(stderr, "sendto() failed on UDP sock (%s)\n", strerror(errno)); } continue; } clock_gettime(CLOCK_MONOTONIC, &tp1); /* get cur time for later calculation */ answer = ethflop_process(&frame, framelen, logmsg); if (answer == NULL) continue; /* wait 0.5 ms - just to make sure that ethflop had time to prepare itself - due to ethflop using a single buffer for send/recv operations it needs a tiny bit of time to switch from 'sending' to 'receiving'. 0.5 ms should be enough even for the slowest PC (4MHz) and the crappiest packet driver (that would need 2000 cycles to return from sending a packet) */ { struct timespec nanot; nanot.tv_sec = 0; nanot.tv_nsec = 500 * 1000; /* 1000 ns is 1 us. 1000 us is 1 ms */ nanosleep(&nanot, NULL); } if (eth_send(answer, sizeof(struct FRAME)) != 0) { syslog(LOG_ERR, "ERROR: send() failed"); } clock_gettime(CLOCK_MONOTONIC, &tp2); /* get cur time */ { /* compute answer time */ long msec_diff; msec_diff = (tp2.tv_sec - tp1.tv_sec) * 1000; msec_diff += tp2.tv_nsec / 1000000l; msec_diff -= tp1.tv_nsec / 1000000l; if (msec_diff > 10) syslog(LOG_WARNING, "WARNING: query %04X took a long time to process (%ld ms) -> [%ld.%ld .. %ld.%ld]", ((unsigned short *)frame)[7], msec_diff, tp1.tv_sec, tp1.tv_nsec, tp2.tv_sec, tp2.tv_nsec); } } /* clean up and quit */ eth_close(); return(0); } ./PaxHeaders/core.h0000644000000000000000000000013214672035463011404 xustar0030 mtime=1726495539.327401489 30 atime=1726495547.035593727 30 ctime=1726495539.327401489 core.h0000644000175000001440000000601514672035463013102 0ustar00mateuszusers00000000000000#ifndef ETHFCORE_H #define ETHFCORE_H #include /* this is a non-standard declaration but apparently it works in clang, gcc * and openwatcom. it instructs the compiler to pack struct tightly * (necessary to avoid "holes" in structs below) */ #pragma pack(push, 1) struct FRAME { unsigned char dmac[6]; unsigned char smac[6]; unsigned short etype; unsigned char protover; unsigned char reqid; char flopid[8]; unsigned short ax; unsigned short bx; unsigned short cx; unsigned short dx; unsigned char sectnum; unsigned char driveid; /* 0=A, 1=B */ unsigned char data[512]; unsigned short csum; }; /* restore the default (compiler/system) struct packing */ #pragma pack(pop) struct cliententry { unsigned char mac[6]; char curflopid[9]; unsigned char ro; /* read-only flag */ int fd; /* open file handle to floppy img (-1 = NONE) */ struct cliententry *next; unsigned short sectcount; /* total count of sectors on floppy */ unsigned short lastimgsector; /* last sector actually in img (thin provisionning) */ unsigned char chs_sectspertrack; unsigned char chs_totheads; unsigned char diskchangeflag; /* 0=disk not changed, 1=new disk in drive */ unsigned char driveid; /* client's local drive letter (0=A 1=B) */ /* below fields are used to cache answers so ethflopd does not have to * recompute (re-read or re-write) data in case of a client retransmission */ unsigned char last_frame_recvd_reqid; /* reqid of last query answered */ unsigned short last_frame_recvd_csum; /* csum of last query answered */ struct FRAME last_frame_sent; /* last answer sent */ }; /* before anything you need to init the ethflop server with the local MAC */ void ethflop_init(const unsigned char *mymac); /* returns a pointer to of frame to send back, or NULL if nothing. log may be * filled with a feedback message. */ const struct FRAME *ethflop_process(const void *frameptr, unsigned short framelen, char *log); /***************************************************************************** * convenience functions useful for user interfaces * *****************************************************************************/ /* returns a ptr to a linked list with all currently connected ethflop clients * returns NULL if no client is connected */ struct cliententry *ethflop_getclients(void); /* insert floppy "arg" to client "ce", readonly if roflag non zero. * msg may be filled with a feedback message * returns 0 on sucess, non-zero otherwise */ int ethflop_insert(struct cliententry *ce, const char *arg, char *msg, unsigned char roflag); /* ejects floppy inserted in client ce and fills msg with a feedback message. * returns 0 on success, non-zero otherwise */ int ethflop_eject(struct cliententry *ce, char *msg); /* fills s with the list of available floppy images, returns the number of * images. image names are written sequentially as 10-bytes fields. sorted. */ unsigned short ethflop_imgfilelist(char *s, unsigned short sz); #endif ./PaxHeaders/version.h0000644000000000000000000000013214672035463012141 xustar0030 mtime=1726495539.327401489 30 atime=1726495547.035593727 30 ctime=1726495539.327401489 version.h0000644000175000001440000000011014672035463013625 0ustar00mateuszusers00000000000000#ifndef PVER #define PVER "20240916" #define PDATE "2019-2024" #endif ./PaxHeaders/ethflopd.txt0000644000000000000000000000013214672035463012651 xustar0030 mtime=1726495539.327401489 30 atime=1726495547.035593727 30 ctime=1726495539.327401489 ethflopd.txt0000644000175000001440000000547514672035463014360 0ustar00mateuszusers00000000000000 ETHFLOPD: the ETHFLOP server http://ethflop.sourceforge.net === INTRODUCTION ============================================================== ETHFLOPD is the server implementation for ETHFLOP, a DOS TSR that emulates a floppy disk drive over Ethernet. The ETHFLOPD server is available for DOS and Linux. === LINUX HOW-TO ============================================================== Quick-start instructions: 1. compile the ethflopd daemon on your Linux machine (type "make") 2. run the ethflopd executable as root: # ./ethflopd eth0 /var/floppies (where eth0 is the system name of your network interface and /var/floppies is the directory where virtual floppy disks will be stored) 3. on the DOS PC, load the ethflop TSR: "ethflop a" 4. create a new 720K diskette: "ethflop n720 myfloppy" 5. "insert" the newly created diskette: "ethflop i myfloppy" 6. you can access the virtual floppy now through A:. Read the program's help for more details (run ethflop without arguments). === DOS HOW-TO ================================================================ You need a PC with an Ethernet card and a suitable packet driver for said card. Run ETHFLOPD.EXE. From now on, PCes on the same LAN should be able to load ETHFLOP and create, access and manage virtual (Ethernet) diskettes: 1. on the client PC, load the ethflop TSR: "ethflop a" 2. create a new 720K diskette: "ethflop n720 myfloppy" 3. "insert" the newly created diskette: "ethflop i myfloppy" 4. you can access the virtual floppy now through A:. Read the program's help for more details (run ethflop without arguments). === LICENSE (MIT) ============================================================= ETHFLOPD, Copyright (C) 2019-2024 Mateusz Viste Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ./PaxHeaders/history.txt0000644000000000000000000000013214672035463012545 xustar0030 mtime=1726495539.327401489 30 atime=1726495547.035593727 30 ctime=1726495539.327401489 history.txt0000644000175000001440000000241414672035463014242 0ustar00mateuszusers00000000000000ver 20240916 - floppy images of non-standard sizes (>2.88M) are created thin-provisionned to avoid ethflopd becoming mute for several seconds while zeroing data, esp relevant for the DOS version on PCs with slow I/O) - Linux version: "client mode" (ethflopd -c) to query and control the daemon - Linux version: lock file no longer used, replaced by a UDP/1390 socket ver 20240907 - when img creation fails, the failed img is removed and clear message shown - logging the time it takes to generate a new floppy image - DOS version: fixed initialization of empty floppy images (reported by ECM) - DOS version: program quits gracefully if no packet driver is detected ver 20240904 - answers are replayed from cache when client retries its query - mounting a floppy with write access is forbidden if anyone uses it already - DOS version: powersaving is optional (/hlt) - DOS version: added the "no UI" mode (/noui) ver 20240902 - created a DOS server (ETHFLOPD.EXE) - support for DMF (1.68M and 1.72M) floppy images - ethflop parameters are case-insensitive - "ethflop l" lists virt. floppies in upcase and many (4 or 8) files per line - ethflopd for linux: all log messages are output through syslog() ver 20191003 - initial public release ./PaxHeaders/Makefile.dos0000644000000000000000000000013214672035463012527 xustar0030 mtime=1726495539.327401489 30 atime=1726495547.035593727 30 ctime=1726495539.327401489 Makefile.dos0000644000175000001440000000113514672035463014223 0ustar00mateuszusers00000000000000# # ethfldos (ETHFLOP server for DOS) Makefile, OpenWatcom # Copyright (C) 2019-2024 Mateusz Viste # # memory segmentation mode (s = small ; c = compact ; m = medium ; l = large) # code | data # small 64K | 64K # compact 64K | 64K+ # medium 64K+ | 64K # large 64K+ | 64K+ MODE = s CFLAGS = -zp2 -lr -we -d0 -y -0 -m$(MODE) -wx -k4096 all: ethflopd.exe ethflopd.exe: ui_dos.c core.c wcl $(CFLAGS) -fe=ethflopd.exe ui_dos.c core.c mdr\mdr$(MODE)2025.lib upx --8086 -9 ethflopd.exe clean: .symbolic del *.obj del *.map del ethflopd.exe ./PaxHeaders/Makefile.linux0000644000000000000000000000013214672035463013101 xustar0030 mtime=1726495539.327401489 30 atime=1726495547.035593727 30 ctime=1726495539.327401489 Makefile.linux0000644000175000001440000000071014672035463014573 0ustar00mateuszusers00000000000000# # ethflop-server-linux makefile # http://ethflop.sourceforge.net # # Copyright (C) 2019-2024 Mateusz Viste # # uncomment for debug #CC = clang #CFLAGS = -O2 -Wall -std=gnu89 -pedantic -Wextra -Wformat-security -D_FORTIFY_SOURCE=1 -Wno-variadic-macros -Wno-padded # production CC ?= gcc CFLAGS ?= -O2 -std=gnu89 all: ethflopd ethflopd: ui_posix.c core.c $(CC) $(CFLAGS) -o ethflopd ui_posix.c core.c clean: rm -f ethflopd *.o