fruit-popper/GDLIB/TOOLBOX.PAS
2021-07-07 17:10:18 -04:00

514 lines
14 KiB
Plaintext

{ Miscellaneous helpers and utilities.
Gered King, 2018 }
{$A+,B-,F-,G+,I-,N+,P-,Q-,R-,S-,T-,V-,X+}
unit Toolbox;
interface
uses FixedP;
const
Bit0 = $0001;
Bit1 = $0002;
Bit2 = $0004;
Bit3 = $0008;
Bit4 = $0010;
Bit5 = $0020;
Bit6 = $0040;
Bit7 = $0080;
Bit8 = $0100;
Bit9 = $0200;
Bit10 = $0400;
Bit11 = $0800;
Bit12 = $1000;
Bit13 = $2000;
Bit14 = $4000;
Bit15 = $8000;
BiosTicksPerSec = 1000.0 / 55.0;
BiosTicksPerSecFP = trunc((1000.0 / 55.0) * FP_FLOAT_SHIFT);
type
{ convenience for accessing a byte pointer as an array (cast to this) }
ByteArray = array[0..63999] of byte;
PByteArray = ^ByteArray;
WordArray = array[0..31999] of byte;
PWordArray = ^WordArray;
{ convenient way to go from pointer <-> segment/offset automatically }
PointerEx = record
case Integer of
0: (ptr: Pointer);
1: (ofs, seg: Word);
end;
var
BiosTicks: word absolute $0040:$006c;
function ByteFlipWord(x : word) : word;
function ByteFlipDWord(x : longint) : longint;
function SignInt(x: integer) : integer;
procedure SwapInts(var a, b: integer);
function Max(a, b: integer) : integer;
function Min(a, b: integer) : integer;
function Fequ(a, b: single) : boolean;
function FequX(a, b, tolerance: single) : boolean;
function ClampInt(value, low, high: integer) : integer;
function ClampFloat(value, low, high: single) : single;
function ClampFixed(value, low, high: fixed) : fixed;
function ScaleRange(value, oldMin, oldMax, newMin, newMax: single) : single;
function ScaleRangeFixed(value, oldMin, oldMax, newMin, newMax: fixed) : fixed;
function BiosTimeSeconds : single;
function BiosTimeSecondsFP : fixed;
function PtrSeg(const p: pointer) : word;
function PtrOfs(const p: pointer) : word;
procedure MemCopy(const dest, src: pointer; bytes: word);
procedure MemCopy16(const dest, src: pointer; bytes: word);
procedure MemFill(const dest: pointer; value: byte; bytes: word);
function HashString(const s: string) : word;
function PackBytes(const src: pointer; var dest: file; srcLength: word) : boolean;
function UnpackBytes(var src: file; const dest: pointer; unpackedSize: integer) : boolean;
implementation
function ByteFlipWord(x : word) : word;
{ returns the value with its bytes flipped, changing its endianess }
assembler;
asm
mov ax, x
xchg al, ah
end;
function ByteFlipDWord(x : longint) : longint;
{ returns the value with its bytes flipped, changing its endianess }
assembler;
asm
mov dx, word(x);
mov ax, word(x+2);
xchg al, ah
xchg dl, dh
end;
function SignInt(x: integer) : integer;
{ return 1 if x is positive, -1 if x is negative, or 0 if x is zero. }
assembler;
asm
mov bx, x
xor ax, ax
test bx, bx
jz @done { if x == 0, then return 0 }
mov ax, 1 { assume x is positive (return 1) }
and bx, 8000h { check sign bit }
jz @done { if sign bit == 0, return 1 (x is positive) }
mov ax, -1 { x is negative, return -1 }
@done:
end;
procedure SwapInts(var a, b: integer);
{ swaps the values of a and b }
var
temp: integer;
begin
temp := a;
a := b;
b := temp;
end;
function Max(a, b: integer) : integer;
{ returns the highest of the two given integers }
begin
if b > a then Max := b else Max := a;
end;
function Min(a, b: integer) : integer;
{ returns the lowest of the two given integers }
begin
if b < a then Min := b else Min := a;
end;
function Fequ(a, b: single) : boolean;
begin
Fequ := (abs(a - b) <= 0.00005);
end;
function FequX(a, b, tolerance: single) : boolean;
begin
FequX := (abs(a - b) <= tolerance);
end;
function ClampInt(value, low, high: integer) : integer;
{ returns the given value, clamped to fall within the low-high range. }
begin
if value < low then
ClampInt := low
else if value > high then
ClampInt := high
else
ClampInt := value;
end;
function ClampFloat(value, low, high: single) : single;
{ returns the given value, clamped to fall within the low-high range. }
begin
if value < low then
ClampFloat := low
else if value > high then
ClampFloat := high
else
ClampFloat := value;
end;
function ClampFixed(value, low, high: fixed) : fixed;
{ returns the given value, clamped to fall within the low-high range. }
begin
if value < low then
ClampFixed := low
else if value > high then
ClampFixed := high
else
ClampFixed := value;
end;
function ScaleRange(value, oldMin, oldMax, newMin, newMax: single) : single;
{ takes a value that should be between oldMin and oldMax, and scales it so
that it is within newMin and newMax at the same relative position within
the new min/max range }
begin
ScaleRange := (newMax - newMin) *
(value - oldMin) /
(oldMax - oldMin) + newMin;
end;
function ScaleRangeFixed(value, oldMin, oldMax, newMin, newMax: fixed) : fixed;
{ takes a value that should be between oldMin and oldMax, and scales it so
that it is within newMin and newMax at the same relative position within
the new min/max range }
begin
ScaleRangeFixed := FixDiv(
FixMul((newMax - newMin), (value - oldMin)),
(oldMax - oldMin) + newMin
);
end;
function BiosTimeSeconds : single;
{ returns the current bios tick count in seconds (time since midnight) }
begin
BiosTimeSeconds := BiosTicks / BiosTicksPerSec;
end;
function BiosTimeSecondsFP : fixed;
{ returns the current bios tick count in seconds (time since midnight) }
begin
BiosTimeSecondsFP := FixDiv(IntToFix(BiosTicks), BiosTicksPerSecFP);
end;
{ TODO: is there some better built-in way to do what the below two functions,
PtrSeg and PtrOfs, do? ... }
function PtrSeg(const p: pointer) : word;
{ returns the segment portion of the memory address in the given pointer }
assembler;
asm
mov ax, word [p+2]
end;
function PtrOfs(const p: pointer) : word;
{ returns the offset portion of the memory address in the given pointer }
assembler;
asm
mov ax, word [p]
end;
procedure MemCopy(const dest, src: pointer;
bytes: word);
{ copy specified number of bytes from src to dest. uses a 32-bit copy
via 'rep movsd' }
assembler;
asm
push ds
db $66,$33,$c9 { xor ecx, ecx }
mov cx, bytes
les di, dest
lds si, src
mov bx, cx
shr cx, 2 { cx = number of dwords to copy }
and bx, 3 { bx = number of remainder bytes to copy }
db $f3,$66,$a5 { rep movsd }
mov cx, bx
rep movsb
@done:
pop ds
end;
procedure MemCopy16(const dest, src: pointer;
bytes: word);
{ copy specified number of bytes from src to dest. uses 16-bit copy
via 'rep movsw' }
assembler;
asm
push ds
xor cx, cx
mov cx, bytes
les di, dest
lds si, src
mov bx, cx
shr cx, 1 { cx = number of words to copy }
and bx, 1 { bx = number of remainder bytes to copy }
rep movsw
mov cx, bx
rep movsb
@done:
pop ds
end;
procedure MemFill(const dest: pointer;
value: byte;
bytes: word);
{ fill the specified length of memory starting at dest with the given value }
assembler;
asm
db $66,$33,$c9 { xor ecx, ecx }
mov cx, bytes
mov al, value
les di, dest
mov ah, al { set all bytes of eax with value to fill with }
db $66 { shl ax => shl eax }
shl ax, 8
mov al, ah
db $66 { shl ax => shl eax }
shl ax, 8
mov al, ah
mov bx, cx
shr cx, 2 { cx = number of dwords to set }
and bx, 3 { bx = number of remainder bytes to set }
db $f3,$66,$ab { rep stosd }
mov cx, bx
rep stosb
end;
function HashString(const s: string) : word;
{ computes the hash of a string, using the djb2 algorithm }
var
hash : word;
i, c, len : integer;
begin
len := length(s);
for i := 1 to len do begin
c := ord(s[i]);
hash := ((hash shl 5) + hash) + c;
end;
HashString := hash;
end;
function PackBytes(const src: pointer;
var dest: file;
srcLength: word) : boolean;
{ packs the bytes located at the given pointer using the PackBits algorithm.
the packed output is written to the destination file as it is being
packed. srcLength is the size of the unpacked (original) data. packing
(and writing to the file) will stop once that many bytes have been read
from the source pointer. returns true on success, or false if there was
an IO error. assumes that the record size for the file being written is
set to 1.
this routine is based on PACKBITS.C from the Animator-Pro sources. }
const
MIN_RUN = 3;
MAX_RUN = 128;
MAX_BUFFER = 128;
type
PackMode = (PackDump, PackRun);
var
srcBytes : ^byte;
b, lastb : byte;
n, runStart : integer;
buffer : array[0..((MAX_RUN*2)-1)] of byte;
mode : PackMode;
fdata : byte;
label ioError;
begin
srcBytes := src;
mode := PackDump;
runStart := 0;
{ read initial source byte to start things off }
lastb := srcBytes^;
buffer[0] := lastb;
inc(srcBytes);
n := 1;
dec(srcLength);
while srcLength > 0 do begin
{ read next byte, add it to the temp buffer }
b := srcBytes^;
inc(srcBytes);
buffer[n] := b;
inc(n);
if mode = PackDump then begin
{ check if we need to flush the temp buffer to the file }
if n > MAX_BUFFER then begin
fdata := n - 2;
BlockWrite(dest, fdata, 1);
if IOResult <> 0 then goto ioError;
BlockWrite(dest, buffer, n-1);
if IOResult <> 0 then goto ioError;
buffer[0] := b;
n := 1;
runStart := 0;
{ detect the start of a run of identical bytes }
end else if b = lastb then begin
if (n - runStart) >= MIN_RUN then begin
if runStart > 0 then begin
{ we've found a run, flush the buffer we have currently and then
switch to "run" mode }
fdata := runStart - 1;
BlockWrite(dest, fdata, 1);
if IOResult <> 0 then goto ioError;
BlockWrite(dest, buffer, runStart);
if IOResult <> 0 then goto ioError;
end;
mode := PackRun;
end else if runStart = 0 then begin
mode := PackRun;
end;
end else begin
runStart := n-1;
end;
end else begin
{ detect the end of a run of identical bytes }
if (b <> lastb) or ((n - runStart) > MAX_RUN) then begin
{ the identical byte run has ended, write it to the file
(just two bytes, the count and the actual byte) }
fdata := -(n - runStart - 2);
BlockWrite(dest, fdata, 1);
if IOResult <> 0 then goto ioError;
fdata := lastb;
BlockWrite(dest, fdata, 1);
if IOResult <> 0 then goto ioError;
{ clear the temp buffer for our switch back to "dump" mode }
buffer[0] := b;
n := 1;
runStart := 0;
mode := PackDump;
end;
end;
lastb := b;
dec(srcLength);
end;
{ the source bytes have all been read, but we still might have to
flush our temp buffer or finish writing out a run of identical bytes }
if mode = PackDump then begin
fdata := n - 1;
BlockWrite(dest, fdata, 1);
if IOResult <> 0 then goto ioError;
BlockWrite(dest, buffer, n);
if IOResult <> 0 then goto ioError;
end else begin
fdata := -(n - runStart - 1);
BlockWrite(dest, fdata, 1);
if IOResult <> 0 then goto ioError;
fdata := lastb;
BlockWrite(dest, fdata, 1);
if IOResult <> 0 then goto ioError;
end;
n := IOResult; { clear i/o error flag }
PackBytes := true;
exit;
ioError:
n := IOResult; { clear i/o error flag }
PackBytes := false;
end;
function UnpackBytes(var src: file;
const dest: pointer;
unpackedSize: integer) : boolean;
{ unpacks a stream of bytes from a file into the destination buffer using
the PackBits algorithm. unpackedSize is the expected size of the
unpacked data, reading/unpacking will stop once this many bytes have been
written to the destination buffer. this function assumes that the file
will contain this much data and will not reach EOF before then. returns
true on success, or false if there was an IO error.
assumes that the record size for the file being read is 1.
this routine is based on PACKBITS.C from the Animator-Pro sources. }
var
destBytes : ^byte;
size, n : integer;
fdata, runLength : byte;
label ioError;
begin
destBytes := dest;
size := 0;
while size < unpackedSize do begin
{ read next "code" byte (run-length byte) that determines how to process
the subsequent bytes }
BlockRead(src, runLength, 1);
if IOResult <> 0 then goto ioError;
{ 129-255 = repeat next byte in file 257-n times }
if runLength > 128 then begin
runLength := 257 - runLength;
{ read the next byte and repeat it }
BlockRead(src, fdata, 1);
if IOResult <> 0 then goto ioError;
MemFill(destBytes, fdata, runLength);
inc(destBytes, runLength);
inc(size, runLength);
{ 0-128 = copy next n-1 bytes in file as-is }
end else if runLength < 128 then begin
inc(runLength);
{ read next set of bytes directly into destination buffer }
BlockRead(src, destBytes^, runLength);
if IOResult <> 0 then goto ioError;
inc(destBytes, runLength);
inc(size, runLength);
end;
{ 128 = no-op (does this even ever appear in any files??) }
end;
n := IOResult; { clear i/o error flag }
UnpackBytes := true;
exit;
ioError:
n := IOResult; { clear i/o error flag }
UnpackBytes := false;
end;
begin
if Test8086 < 2 then begin
writeln('Toolbox unit requires a 386 cpu or higher!');
halt;
end;
end.