AmendHub

jcs

/

wallops

/

amendments

/

1

wallops: Initial import


jcs made amendment 1 6 months ago
--- AddressXlation.h Fri Sep 18 08:57:06 2020 +++ AddressXlation.h Fri Sep 18 08:57:06 2020 @@ -0,0 +1,74 @@ +/* + AddressXlation.h + MacTCP name to address translation routines. + + Copyright Apple Computer, Inc. 1988 + All rights reserved + +*/ + +#ifndef __ADDRESSXLATION__ +#define __ADDRESSXLATION__ + +#include "MacTCP.h" + +#define NUM_ALT_ADDRS 4 + +typedef struct hostInfo { + int _pad; /* XXX: i don't know why this is needed, but without it, + * StrToAddrProcPtr() returns everything shifted 2 bytes */ + int rtnCode; + char cname[255]; + unsigned long addr[NUM_ALT_ADDRS]; +}; + +typedef enum AddrClasses { + A = 1, + NS, + CNAME = 5, + lastClass = 65535 +}; + +typedef struct cacheEntryRecord { + char *cname; + unsigned short type; + enum AddrClasses class; + unsigned long ttl; + union { + char *name; + ip_addr addr; + } rdata; +}; + +typedef pascal void (*EnumResultProcPtr)(struct cacheEntryRecord *cacheEntryRecordPtr, + char *userDataPtr); +typedef pascal void (*ResultProcPtr)(struct hostInfo *hostInfoPtr, + char *userDataPtr); + +extern OSErr OpenResolver(char *fileName); +extern OSErr StrToAddr(char *hostName, struct hostInfo *hostInfoPtr, + ResultProcPtr resultProc, char *userDataPtr); +extern OSErr AddrToStr(unsigned long addr, char *addrStr); +extern OSErr EnumCache(EnumResultProcPtr enumResultProc, char *userDataPtr); +extern OSErr AddrToName(ip_addr addr, struct hostInfo *hostInfoPtr, + ResultProcPtr resultProc, char *userDataPtr); +extern OSErr CloseResolver(void); + +/* + * The above functions call into the dnr resource and pass the function id + * and function-specific arguments, so the compiler needs to know the types + */ +typedef OSErr (*OpenResolverProcPtr)(UInt32, char *); +typedef OSErr (*CloseResolverProcPtr)(UInt32); +typedef OSErr (*StrToAddrProcPtr)(UInt32, char *hostName, + struct hostInfo *hostInfoPtr, ResultProcPtr resultProc, + char *userDataPtr); +typedef OSErr (*AddrToStrProcPtr)(UInt32, unsigned long addr, + char *addrStr); +typedef OSErr (*EnumCacheProcPtr)(UInt32, EnumResultProcPtr enumResultProc, + char *userDataPtr); +typedef OSErr (*AddrToNameProcPtr)(UInt32, ip_addr addr, + struct hostInfo *hostInfoPtr, ResultProcPtr resultProc, + char *userDataPtr); + +#endif --- chatter.c Sun Jan 30 13:44:20 2022 +++ chatter.c Sun Jan 30 13:44:20 2022 @@ -0,0 +1,406 @@ +/* + * Copyright (c) 2021-2022 joshua stein <jcs@jcs.org> + * + * Permission to use, copy, modify, and 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. + */ + +#include <stdio.h> +#include <stdarg.h> +#include <string.h> +#include "chatter.h" +#include "irc.h" +#include "util.h" + +#define NICK_LIST_WIDTH 100 + +void chatter_update_titlebar(struct chatter *chatter); +void chatter_layout(struct chatter *chatter, bool init); +void chatter_key_down(struct focusable *focusable, EventRecord *event); +void chatter_mouse_down(struct focusable *focusable, EventRecord *event); +void chatter_idle(struct focusable *focusable, EventRecord *event); +void chatter_update(struct focusable *focusable, EventRecord *event); +void chatter_close(struct focusable *focusable, EventRecord *event); +void chatter_atexit(struct focusable *focusable); + +void +chatter_init(const char *server, const unsigned short port, + const char *nick) +{ + struct focusable *focusable; + struct chatter *chatter; + char title[64]; + Rect bounds = { 0 }, control_bounds = { 0 }; + Rect data_bounds = { 0, 0, 0, 1 }; /* tlbr */ + Point cell_size = { 0 }; + Cell cell = { 0 }; + + if (_TCPInit() != 0) + panic("TCPInit failed"); + + chatter = xmalloczero(sizeof(struct chatter)); + chatter->irc_state = IRC_STATE_UNINITED; + chatter->irc_hostname = xstrdup(server); + chatter->irc_port = port; + chatter->irc_nick = xstrdup(nick); + + bounds.left = 10; + bounds.top = screenBits.bounds.top + 10 + (GetMBarHeight() * 2) - 1; + bounds.right = screenBits.bounds.right - 11; + bounds.bottom = screenBits.bounds.bottom - 11; + + snprintf(title, sizeof(title), "%s: Disconnected", PROGRAM_NAME); + chatter->win = NewWindow(0L, &bounds, CtoPstr(title), false, + documentProc, (WindowPtr)-1L, true, 0); + if (!chatter->win) + panic("Can't create chatter window"); + + SetPort(chatter->win); + TextFont(applFont); + TextSize(10); + + /* initially draw everything off-screen until we chatter_layout() */ + bounds.left = chatter->win->portRect.right + 10; + bounds.top = chatter->win->portRect.top + 10; + bounds.right = bounds.left + 20; + bounds.bottom = bounds.top + 20; + chatter->win_init_bounds = bounds; + + /* input */ + chatter->input_te = TENew(&bounds, &bounds); + + /* nick list */ + chatter->nick_list = LNew(&bounds, &data_bounds, cell_size, 0, + chatter->win, true, true, false, true); + if (!chatter->nick_list) + panic("Can't create mailboxes list"); + (*(chatter->nick_list))->selFlags = lOnlyOne | lNoNilHilite; + + /* nick scroller */ + chatter->nick_scroller = NewControl(chatter->win, &bounds, "\p", true, + 1, 1, 1, scrollBarProc, 0L); + + /* messages */ + chatter->messages_te = TENew(&bounds, &bounds); + + /* messages scrollbar */ + chatter->messages_scroller = NewControl(chatter->win, &bounds, "\p", + true, 1, 1, 1, scrollBarProc, 0L); + + focusable = xmalloczero(sizeof(struct focusable)); + focusable->win = chatter->win; + focusable->cookie = chatter; + focusable->idle = chatter_idle; + focusable->key_down = chatter_key_down; + focusable->mouse_down = chatter_mouse_down; + focusable->update = chatter_update; + focusable->close = chatter_close; + focusable->atexit = chatter_atexit; + show_focusable(focusable); + chatter_layout(chatter, false); + + UpdateScrollbarForTE(chatter->messages_scroller, chatter->messages_te, + false); + + chatter_output(chatter, "*** Welcome to %s\r", PROGRAM_NAME); +} + +void +chatter_layout(struct chatter *chatter, bool init) +{ + Rect bounds, win_bounds; + + if (init) + win_bounds = chatter->win_init_bounds; + else + win_bounds = chatter->win->portRect; + + /* input */ + bounds.left = 0; + bounds.right = win_bounds.right - SCROLLBAR_WIDTH; + bounds.top = win_bounds.bottom - SCROLLBAR_WIDTH; + bounds.bottom = win_bounds.bottom; + (*(chatter->input_te))->viewRect = bounds; + InsetRect(&bounds, 3, 1); + (*(chatter->input_te))->destRect = bounds; + TECalText(chatter->input_te); + + /* nick scrollbar */ + bounds.top = -1; + bounds.right = win_bounds.right + 1; + bounds.left = bounds.right - SCROLLBAR_WIDTH; + bounds.bottom = (*(chatter->input_te))->viewRect.top - 1; + (*(chatter->nick_scroller))->contrlRect = bounds; + + /* nick list */ + bounds.top = 0; + bounds.right = (*(chatter->nick_scroller))->contrlRect.left; + bounds.left = bounds.right - NICK_LIST_WIDTH; + bounds.bottom = (*(chatter->input_te))->viewRect.top - 2; + (*(chatter->nick_list))->rView = bounds; + + /* messages scrollbar */ + bounds.top = -1; + bounds.right = (*(chatter->nick_list))->rView.left; + bounds.left = bounds.right - SCROLLBAR_WIDTH; + bounds.bottom = (*(chatter->input_te))->viewRect.top - 1; + (*(chatter->messages_scroller))->contrlRect = bounds; + + /* messages */ + bounds.right = (*(chatter->messages_scroller))->contrlRect.left; + bounds.left = 0; + bounds.top = 0; + bounds.bottom = (*(chatter->input_te))->viewRect.top - 2; + (*(chatter->messages_te))->viewRect = bounds; + InsetRect(&bounds, 4, 4); + (*(chatter->messages_te))->destRect = bounds; + TECalText(chatter->messages_te); + + InvalRect(chatter->win->visRgn); +} + +void +chatter_close(struct focusable *focusable, EventRecord *event) +{ + struct chatter *chatter = (struct chatter *)(focusable->cookie); + + irc_close(chatter); + close_focusable(focusable); +} + +void +chatter_atexit(struct focusable *focusable) +{ + struct chatter *chatter = (struct chatter *)(focusable->cookie); + + irc_abort(chatter); +} + +void +chatter_update_titlebar(struct chatter *chatter) +{ + char title[64]; + + snprintf(title, sizeof(title), "%s: %s@%s", PROGRAM_NAME, + chatter->irc_nick, chatter->irc_hostname); + SetWTitle(chatter->win, CtoPstr(title)); +} + +void +chatter_idle(struct focusable *focusable, EventRecord *event) +{ + struct chatter *chatter = (struct chatter *)(focusable->cookie); + + TEIdle(chatter->messages_te); + TEIdle(chatter->input_te); + irc_process(chatter); +} + +void +chatter_update(struct focusable *focusable, EventRecord *event) +{ + struct chatter *chatter = (struct chatter *)(focusable->cookie); + GrafPtr old_port; + Rect r; + short what = -1; + + GetPort(&old_port); + + if (event != NULL) + what = event->what; + + switch (what) { + case -1: + case updateEvt: + TextFont(applFont); + TextSize(10); + + EraseRect(&chatter->win->portRect); + + r = (*(chatter->nick_list))->rView; + EraseRect(&r); + LUpdate(chatter->win->visRgn, chatter->nick_list); + InsetRect(&r, -1, -1); + FrameRect(&r); + + r = (*(chatter->messages_te))->viewRect; + InsetRect(&r, -1, -1); + FrameRect(&r); + + TEUpdate(&(*(chatter->messages_te))->viewRect, + chatter->messages_te); + TEUpdate(&(*(chatter->input_te))->viewRect, chatter->messages_te); + + DrawControls(chatter->win); + break; + case activateEvt: + if (event->modifiers & activeFlag) { + LActivate(true, chatter->nick_list); + TEActivate(chatter->input_te); + } else { + LActivate(false, chatter->nick_list); + TEDeactivate(chatter->input_te); + } + break; + } +} + +void +chatter_mouse_down(struct focusable *focusable, EventRecord *event) +{ + struct chatter *chatter = (struct chatter *)(focusable->cookie); + Point p; + Cell selected = { 0 }, now = { 0 }, t = { 0 }; + ControlHandle control; + Rect r; + int val, adj, page, ret; + short part; + + p = event->where; + GlobalToLocal(&p); + + r = (*(chatter->nick_list))->rView; + r.right += SCROLLBAR_WIDTH; + if (PtInRect(p, &r)) { + /* store what is selected now */ + ret = LGetSelect(true, &selected, chatter->nick_list); + + /* possibly highlight a new cell */ + LClick(p, event->modifiers, chatter->nick_list); + + if (selected.v != now.v) { + LSetSelect(false, selected, chatter->nick_list); + /* TODO */ + } + + return; + } + + r = (*(chatter->messages_te))->viewRect; + if (PtInRect(p, &r)) { + TEClick(p, ((event->modifiers & shiftKey) != 0), + chatter->messages_te); + return; + } + + r = (*(chatter->input_te))->viewRect; + if (PtInRect(p, &r)) { + TEClick(p, ((event->modifiers & shiftKey) != 0), + chatter->input_te); + return; + } + + switch (part = FindControl(p, chatter->win, &control)) { + case inUpButton: + case inDownButton: + case inPageUp: + case inPageDown: + if (control != chatter->messages_scroller) + break; + SetTrackControlTE(chatter->messages_te); + TrackControl(control, p, TrackMouseDownInControl); + break; + case inThumb: + val = GetCtlValue(control); + if (TrackControl(control, p, 0L) == 0) + break; + adj = val - GetCtlValue(control); + if (adj != 0) { + val -= adj; + if (control == chatter->messages_scroller) + TEScroll(0, adj * TEGetHeight(0, 0, chatter->messages_te), + chatter->messages_te); + SetCtlValue(control, val); + } + break; + } +} + +#if 0 +void +chatter_grow(Point p) +{ + GrafPtr old_port; + long res; + Rect r; + + GetPort(&old_port); + + SetPort(chatter_win); + SetRect(&r, 80, 80, screenBits.bounds.right, screenBits.bounds.bottom); + res = GrowWindow(chatter_win, p, &r); + if (res != 0) { + SizeWindow(chatter_win, LoWord(res), HiWord(res), false); + InvalRect(&chatter_win->portRect); + chatter_layout(); + } + + SetPort(old_port); +} +#endif + +void +chatter_key_down(struct focusable *focusable, EventRecord *event) +{ + struct chatter *chatter = (struct chatter *)(focusable->cookie); + char k; + + k = (event->message & charCodeMask); + TEKey(k, chatter->input_te); +} + +size_t +chatter_output(struct chatter *chatter, const char *format, ...) +{ + va_list argptr; + size_t len = 0, last_pos, last_line, i; + short vlines, tries; + char didup = 0; + + for (tries = 1; tries <= 3; tries++) { + if (chatter->message_line_size == 0) { + chatter->message_line_size = 1024 * tries; + chatter->message_line = xNewHandle(chatter->message_line_size); + } + + va_start(argptr, format); + HLock(chatter->message_line); + /* TODO: vsnprintf */ + len = vsprintf(*(chatter->message_line), format, argptr); + va_end(argptr); + HUnlock(chatter->message_line); + + if (len < chatter->message_line_size) + break; + + DisposHandle(chatter->message_line); + chatter->message_line = NULL; + chatter->message_line_size = 0; + } + + if (chatter->message_line_size == 0) + panic("Can't build message line of size %lu", len); + + HLock(chatter->message_line); + TESetSelect(1024 * 32, 1024 * 32, chatter->messages_te); + TEInsert(*(chatter->message_line), len, chatter->messages_te); + HUnlock(chatter->message_line); + + if (tries > 1) { + /* shrink */ + DisposHandle(chatter->message_line); + chatter->message_line = NULL; + chatter->message_line_size = 0; + } + + return len; +} --- chatter.h Sun Jan 30 14:35:31 2022 +++ chatter.h Sun Jan 30 14:35:31 2022 @@ -0,0 +1,55 @@ +#include <stdio.h> + +#include "tcp.h" +#include "util.h" + +#define PROGRAM_NAME "wallops" + +struct focusable { + WindowPtr win; + void *cookie; + void (*idle)(struct focusable *focusable, EventRecord *event); + void (*update)(struct focusable *focusable, EventRecord *event); + void (*key_down)(struct focusable *focusable, EventRecord *event); + void (*mouse_down)(struct focusable *focusable, EventRecord *event); + void (*close)(struct focusable *focusable, EventRecord *event); + void (*suspend)(struct focusable *focusable, EventRecord *event); + void (*resume)(struct focusable *focusable, EventRecord *event); + void (*atexit)(struct focusable *focusable); +}; +extern struct focusable *cur_focusable; +extern struct focusable *last_focusable; + +struct chatter { + WindowPtr win; + Rect win_init_bounds; + TEHandle messages_te; + ControlHandle messages_scroller; + TEHandle input_te; + ListHandle nick_list; + ControlHandle nick_scroller; + Handle message_line; + size_t message_line_size; + + short irc_state; + char *irc_hostname; + short irc_port; + char *irc_nick; + TCPiopb irc_rcv_pb, irc_send_pb, irc_close_pb; + TCPStatusPB irc_status_pb; + StreamPtr irc_stream; + wdsEntry irc_wds[2]; + unsigned char irc_obuf[512]; + unsigned char irc_ibuf[512]; /* RFC2812 says max line will be 512 */ + short irc_obuflen; + short irc_ibuflen; + /* docs say 4*MTU+1024, but MTU will probably be <1500 */ + unsigned char irc_tcp_buf[(4 * 1500) + 1024]; +}; + +void show_focusable(struct focusable *focusable); +void close_focusable(struct focusable *focusable); + +void chatter_init(const char *server, const unsigned short port, + const char *nick); +size_t chatter_output(struct chatter *chatter, const char *format, ...); --- dnr.c Thu Sep 17 14:11:02 2020 +++ dnr.c Thu Sep 17 14:11:02 2020 @@ -0,0 +1,308 @@ +/* + * DNR.c - DNR library for MPW + * + * (c) Copyright 1988 by Apple Computer. All rights reserved + * + * Modifications by Jim Matthews, Dartmouth College, 5/91 + */ + +#include <OSUtils.h> +#include <Files.h> +#include <Folders.h> +#include <GestaltEqu.h> +#include <Traps.h> + +#include "AddressXlation.h" + +#define OPENRESOLVER 1 +#define CLOSERESOLVER 2 +#define STRTOADDR 3 +#define ADDRTOSTR 4 +#define ENUMCACHE 5 +#define ADDRTONAME 6 +#define HINFO 7 +#define MXINFO 8 + +TrapType GetTrapType(unsigned long theTrap); +Boolean TrapAvailable(unsigned long trap); +void GetSystemFolder(short *vRefNumP, long *dirIDP); +void GetCPanelFolder(short *vRefNumP, long *dirIDP); +short SearchFolderForDNRP(long targetType, long targetCreator, + short vRefNum, long dirID); +short OpenOurRF(void); + +Handle dnr = nil; + +TrapType +GetTrapType(unsigned long theTrap) +{ + if (BitAnd(theTrap, 0x0800) > 0) + return (ToolTrap); + + return (OSTrap); +} + +Boolean +TrapAvailable(unsigned long trap) +{ + TrapType trapType = ToolTrap; + unsigned long numToolBoxTraps; + + if (NGetTrapAddress(_InitGraf, ToolTrap) == + NGetTrapAddress(0xAA6E, ToolTrap)) + numToolBoxTraps = 0x200; + else + numToolBoxTraps = 0x400; + + trapType = GetTrapType(trap); + if (trapType == ToolTrap) { + trap = BitAnd(trap, 0x07FF); + if (trap >= numToolBoxTraps) + trap = _Unimplemented; + } + + return (NGetTrapAddress(trap, trapType) != + NGetTrapAddress(_Unimplemented, ToolTrap)); +} + +void +GetSystemFolder(short *vRefNumP, long *dirIDP) +{ + SysEnvRec info; + long wdProcID; + + SysEnvirons(1, &info); + if (GetWDInfo(info.sysVRefNum, vRefNumP, dirIDP, &wdProcID) != noErr) { + *vRefNumP = 0; + *dirIDP = 0; + } +} + +void +GetCPanelFolder(short *vRefNumP, long *dirIDP) +{ + Boolean hasFolderMgr = false; + long feature; + + if (TrapAvailable(_GestaltDispatch) && + Gestalt(gestaltFindFolderAttr, &feature) == noErr) + hasFolderMgr = true; + + if (!hasFolderMgr) { + GetSystemFolder(vRefNumP, dirIDP); + return; + } + + if (FindFolder(kOnSystemDisk, kControlPanelFolderType, + kDontCreateFolder, vRefNumP, dirIDP) != noErr) { + *vRefNumP = 0; + *dirIDP = 0; + } +} + +/* + * SearchFolderForDNRP is called to search a folder for files that might + * contain the 'dnrp' resource + */ +short +SearchFolderForDNRP(long targetType, long targetCreator, short vRefNum, + long dirID) +{ + HParamBlockRec fi; + Str255 filename; + short refnum; + + fi.fileParam.ioCompletion = nil; + fi.fileParam.ioNamePtr = filename; + fi.fileParam.ioVRefNum = vRefNum; + fi.fileParam.ioDirID = dirID; + fi.fileParam.ioFDirIndex = 1; + + while (PBHGetFInfo(&fi, false) == noErr) { + /* scan system folder for driver resource files of specific type & creator */ + if (fi.fileParam.ioFlFndrInfo.fdType == targetType && + fi.fileParam.ioFlFndrInfo.fdCreator == targetCreator) { + /* found the MacTCP driver file? */ + refnum = HOpenResFile(vRefNum, dirID, filename, fsRdPerm); + if (Get1IndResource('dnrp', 1)) + return refnum; + CloseResFile(refnum); + } + + /* check next file in system folder */ + fi.fileParam.ioFDirIndex++; + fi.fileParam.ioDirID = dirID; /* PBHGetFInfo() clobbers ioDirID */ + } + return -1; +} + +/* OpenOurRF is called to open the MacTCP driver resources */ +short +OpenOurRF(void) +{ + short refnum; + short vRefNum; + long dirID; + + /* first search Control Panels for MacTCP 1.1 */ + GetCPanelFolder(&vRefNum, &dirID); + refnum = SearchFolderForDNRP('cdev', 'ztcp', vRefNum, dirID); + if (refnum != -1) + return (refnum); + + /* next search System Folder for MacTCP 1.0.x */ + GetSystemFolder(&vRefNum, &dirID); + refnum = SearchFolderForDNRP('cdev', 'mtcp', vRefNum, dirID); + if (refnum != -1) + return (refnum); + + /* finally, search Control Panels for MacTCP 1.0.x */ + GetCPanelFolder(&vRefNum, &dirID); + refnum = SearchFolderForDNRP('cdev', 'mtcp', vRefNum, dirID); + if (refnum != -1) + return (refnum); + + return -1; +} + +OSErr +OpenResolver(char *fileName) +{ + short refnum; + OSErr rc; + + if (dnr != nil) + /* resolver already loaded in */ + return (noErr); + + /* + * Open the MacTCP driver to get DNR resources. Search for it based on + * creator & type rather than simply file name. + */ + refnum = OpenOurRF(); + + /* + * Ignore failures since the resource may have been installed in the + * System file if running on a Mac 512Ke. + */ + + /* load in the DNR resource package */ + ResrvMem(16000L); /* s-dorner@uiuc.edu, 9/5/91 */ + dnr = Get1IndResource('dnrp', 1); + if (dnr == nil) { + /* can't open DNR */ + return (ResError()); + } + + DetachResource(dnr); + if (refnum != -1) { + CloseWD(refnum); + CloseResFile(refnum); + } + + /* lock the DNR resource since it cannot be relocated while opened */ + MoveHHi(dnr); + HLock(dnr); + + /* call open resolver */ + rc = ((OpenResolverProcPtr)*dnr)(OPENRESOLVER, fileName); + if (rc != noErr) { + /* problem with open resolver, flush it */ + HUnlock(dnr); + DisposHandle(dnr); + dnr = nil; + } + + return (rc); +} + +OSErr +CloseResolver(void) +{ + if (dnr == nil) + /* resolver not loaded error */ + return (notOpenErr); + + /* call close resolver */ + (void)((CloseResolverProcPtr)*dnr)(CLOSERESOLVER); + + /* release the DNR resource package */ + HUnlock(dnr); + DisposHandle(dnr); + dnr = nil; + + return (noErr); +} + +OSErr +StrToAddr(char *hostName, struct hostInfo *rtnStruct, + ResultProcPtr resultproc, char *userDataPtr) +{ + OSErr err; + + if (dnr == nil) + /* resolver not loaded error */ + return (notOpenErr); + + err = ((StrToAddrProcPtr)*dnr)(STRTOADDR, hostName, rtnStruct, + resultproc, userDataPtr); + return err; +} + +OSErr +AddrToStr(unsigned long addr, char *addrStr) +{ + if (dnr == nil) + /* resolver not loaded error */ + return (notOpenErr); + + (void)((AddrToStrProcPtr)*dnr)(ADDRTOSTR, addr, addrStr); + return (noErr); +} + +OSErr +EnumCache(EnumResultProcPtr enumResultProc, char *userDataPtr) +{ + if (dnr == nil) + /* resolver not loaded error */ + return (notOpenErr); + + return ((EnumCacheProcPtr)*dnr)(ENUMCACHE, enumResultProc, + userDataPtr); +} + +OSErr +AddrToName(ip_addr addr, struct hostInfo *hostInfoPtr, + ResultProcPtr resultProc, char *userDataPtr) +{ + if (dnr == nil) + /* resolver not loaded error */ + return (notOpenErr); + + return ((AddrToNameProcPtr)*dnr)(ADDRTONAME, addr, hostInfoPtr, + resultProc, userDataPtr); +} + +#if 0 +OSErr +HInfo(char *hostName, struct returnRec *returnRecPtr, long resultProc, + char *userDataPtr) +{ + if (dnr == nil) + /* resolver not loaded error */ + return (notOpenErr); + + return ((*dnr)(HINFO, hostName, returnRecPtr, resultProc, userDataPtr)); +} + +OSErr +MXInfo(char *hostName, struct returnRec *returnRecPtr, long resultProc, + char *userDataPtr) +{ + if (dnr == nil) + /* resolver not loaded error */ + return (notOpenErr); + + return ((*dnr)(MXINFO, hostName, returnRecPtr, resultProc, userDataPtr)); +} +#endif --- irc.c Sun Jan 30 14:33:57 2022 +++ irc.c Sun Jan 30 14:33:57 2022 @@ -0,0 +1,255 @@ +/* + * Copyright (c) 2021-2022 joshua stein <jcs@jcs.org> + * + * Permission to use, copy, modify, and 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. + */ + +#include <string.h> + +#include "chatter.h" +#include "irc.h" + +void irc_init(struct chatter *chatter); +short irc_verify_state(struct chatter *chatter, int state); +short irc_recv(struct chatter *chatter); +short irc_send(struct chatter *chatter); +void irc_process_ibuf(struct chatter *chatter); + +void +irc_process(struct chatter *chatter) +{ + if (chatter->irc_state >= IRC_STATE_CONNECTED) + irc_recv(chatter); + + switch (chatter->irc_state) { + case IRC_STATE_UNINITED: + chatter->irc_state = IRC_STATE_CONNECTING; + irc_init(chatter); + break; + case IRC_STATE_LOGIN: + /* TODO */ + chatter->irc_state = IRC_STATE_CONNECTED; + break; + case IRC_STATE_CONNECTED: + if (chatter->irc_ibuflen) + irc_process_ibuf(chatter); + break; + } +} + +short +irc_verify_state(struct chatter *chatter, int state) +{ + if (chatter->irc_state != state) { + warn("Bad IRC state (in %d, expected %d)", chatter->irc_state, + state); + chatter->irc_state = IRC_STATE_DISCONNECTED; + return 0; + } + + return 1; +} + +void +irc_init(struct chatter *chatter) +{ + char ip_str[] = "255.255.255.255"; + char *retname; + ip_addr ip = 0, local_ip = 0; + ip_port port, local_port = 0; + short err; + + if (!irc_verify_state(chatter, IRC_STATE_CONNECTING)) + return; + + chatter_output(chatter, "*** Connecting to %s:%d...\r", + chatter->irc_hostname, chatter->irc_port); + + if ((err = _TCPCreate(&chatter->irc_send_pb, &chatter->irc_stream, + (Ptr)chatter->irc_tcp_buf, sizeof(chatter->irc_tcp_buf), + nil, nil, nil, false)) != noErr) { + warn("TCPCreate failed: %d", err); + chatter->irc_state = IRC_STATE_DISCONNECTED; + return; + } + + if ((err = TCPResolveName(&chatter->irc_hostname, &ip)) != 0) { + warn("Couldn't resolve host %s (%d)", chatter->irc_hostname, err); + chatter->irc_state = IRC_STATE_DISCONNECTED; + return; + } + + long2ip(ip, ip_str); + + if ((err = _TCPActiveOpen(&chatter->irc_send_pb, chatter->irc_stream, + ip, chatter->irc_port, &local_ip, &local_port, nil, nil, + false)) != noErr) { + warn("Failed connecting to %s (%s) port %d: %d", + chatter->irc_hostname, ip_str, chatter->irc_port, err); + chatter->irc_state = IRC_STATE_DISCONNECTED; + return; + } + + chatter_output(chatter, "*** Connected to %s (%s) port %d\r", + chatter->irc_hostname, ip_str, chatter->irc_port); + + chatter->irc_state = IRC_STATE_LOGIN; +} + +void +irc_abort(struct chatter *chatter) +{ + _TCPClose(&chatter->irc_close_pb, chatter->irc_stream, nil, nil, + false); + _TCPRelease(&chatter->irc_close_pb, chatter->irc_stream, nil, nil, + false); +} + +void +irc_close(struct chatter *chatter) +{ + if (chatter->irc_state == IRC_STATE_DISCONNECTED) + return; + + chatter_output(chatter, "*** Disconnecting\r"); + irc_abort(chatter); + chatter->irc_state = IRC_STATE_DISCONNECTED; +} + +void +irc_process_ibuf(struct chatter *chatter) +{ + size_t n; + + if (chatter->irc_ibuflen == 0) + return; + + /* get one line */ +look_for_line: + for (n = 0; n < chatter->irc_ibuflen - 1; n++) { + if (chatter->irc_ibuf[n] != '\r') + continue; + if (chatter->irc_ibuf[n + 1] != '\n') + continue; + + chatter->irc_ibuf[n] = '\0'; + chatter->irc_ibuf[n + 1] = '\0'; + chatter_output(chatter, "*** %s\r", chatter->irc_ibuf); + + if (n == chatter->irc_ibuflen - 2) { + chatter->irc_ibuflen = 0; + break; + } else { + memmove(chatter->irc_ibuf, chatter->irc_ibuf + n + 2, + chatter->irc_ibuflen - n - 2); + goto look_for_line; + } + } +} + +short +irc_recv(struct chatter *chatter) +{ + unsigned short rlen; + short error, n; + + if (chatter->irc_ibuflen >= sizeof(chatter->irc_ibuf)) + return 0; + + if (chatter->irc_rcv_pb.ioResult > 0) + /* previous _TCPSend/_TCPRecv has not completed yet */ + return 0; + + error = _TCPStatus(&chatter->irc_rcv_pb, chatter->irc_stream, + &chatter->irc_status_pb, nil, nil, false); + if (error) { + irc_close(chatter); + return 0; + } + + if (chatter->irc_status_pb.amtUnreadData == 0) + return 0; + + rlen = chatter->irc_status_pb.amtUnreadData; + if (chatter->irc_ibuflen + rlen >= sizeof(chatter->irc_ibuf)) + rlen = sizeof(chatter->irc_ibuf) - chatter->irc_ibuflen; + + error = _TCPRcv(&chatter->irc_rcv_pb, chatter->irc_stream, + (Ptr)(chatter->irc_ibuf + chatter->irc_ibuflen), &rlen, nil, nil, + false); + if (error) { + chatter_output(chatter, "*** TCPRecv failed: %d\r", error); + irc_close(chatter); + return 0; + } + + chatter->irc_ibuflen += rlen; + return rlen; +} + +short +irc_send(struct chatter *chatter) +{ + short error; + + if (chatter->irc_obuflen == 0 || + chatter->irc_state == IRC_STATE_DISCONNECTED) + return 0; + + if (chatter->irc_send_pb.ioResult > 0) + /* previous _TCPSend has not completed yet */ + return 0; + +process_result: + if (chatter->irc_wds[0].length) { + /* previous _TCPSend completed, shift out those bytes */ + chatter->irc_obuflen -= chatter->irc_wds[0].length; + if (chatter->irc_obuflen < 0) { + warn("bogus obuflen %d", chatter->irc_obuflen); + chatter->irc_obuflen = 0; + } else if (chatter->irc_obuflen > 0) + memmove(chatter->irc_obuf, + chatter->irc_obuf + chatter->irc_wds[0].length, + chatter->irc_obuflen); + + chatter->irc_wds[0].length = 0; + + if (chatter->irc_obuflen == 0) + return 0; + } + + /* + * _TCPSend only knows how many wds pointers were passed in when it + * reads the next one and its pointer is zero (or size is zero?), so + * even though we're only sending one wds, memory after wds[0] + * has to be zeroed out. + */ + memset(&chatter->irc_wds, 0, sizeof(chatter->irc_wds)); + chatter->irc_wds[0].ptr = (Ptr)&chatter->irc_obuf; + chatter->irc_wds[0].length = chatter->irc_obuflen; + + error = _TCPSend(&chatter->irc_send_pb, chatter->irc_stream, + chatter->irc_wds, nil, nil, true); + if (error) { + warn("TCPSend failed: %d", error); + irc_close(chatter); + return 0; + } + + /* if we sent quickly enough, we won't have to cycle again */ + if (chatter->irc_send_pb.ioResult <= 0) + goto process_result; + + return 0; +} + --- irc.h Sun Jan 30 14:20:47 2022 +++ irc.h Sun Jan 30 14:20:47 2022 @@ -0,0 +1,32 @@ +/* + * Copyright (c) 2022 joshua stein <jcs@jcs.org> + * + * Permission to use, copy, modify, and 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. + */ + +#ifndef __IRC_H__ +#define __IRC_H__ + +enum { + IRC_STATE_UNINITED = 0, + IRC_STATE_DISCONNECTED, + IRC_STATE_CONNECTING, + IRC_STATE_CONNECTED, + IRC_STATE_LOGIN +}; + +void irc_process(struct chatter *chatter); +void irc_abort(struct chatter *chatter); +void irc_close(struct chatter *chatter); + +#endif --- MacTCP.h Thu Sep 17 17:48:48 2020 +++ MacTCP.h Thu Sep 17 17:48:48 2020 @@ -0,0 +1,863 @@ +/* + File: MacTCP.h + + Contains: TCP Manager Interfaces. + + Version: Technology: MacTCP 2.0.6 + Package: Universal Interfaces 2.1ß1 in “MPW Prerelease” on ETO #17 + + Copyright: © 1984-1995 by Apple Computer, Inc. + All rights reserved. + + Bugs?: If you find a problem with this file, use the Apple Bug Reporter + stack. Include the file and version information (from above) + in the problem description and send to: + Internet: apple.bugs@applelink.apple.com + AppleLink: APPLE.BUGS + +*/ + +#ifndef __MACTCP__ +#define __MACTCP__ + + +#ifndef __TYPES__ +#include <Types.h> +#endif + +#if 0 +#ifndef __APPLETALK__ +#include <AppleTalk.h> +#endif +#endif + +enum { + kPascalStackBased = 0, + kCStackBased = 0, + kThinkCStackBased = 0 +}; +#define SIZE_CODE(size) (0) +#define RESULT_SIZE(sizeCode) (0) +#define STACK_ROUTINE_PARAMETER(whichParam, sizeCode) (0) + +#ifdef __cplusplus +extern "C" { +#endif + +#if PRAGMA_ALIGN_SUPPORTED +#pragma options align=mac68k +#endif + +#if PRAGMA_IMPORT_SUPPORTED +#pragma import on +#endif + +/* +Developer Notes: + 0. This MacTCP header replaces what used to be defined in the following header files + MacTCPCommonTypes.h + GetMyIPAddr.h + MiscIPPB.h + TCPPB.h + UDPPB.h + + When the various control calls are made to the ip driver, you must set up a + NewRoutineDescriptor for every non-nil completion routine and/or notifyProc parameter. + Otherwise, the 68K driver code, will not correctly call your routine. + 1. For ipctlGetAddr Control calls, use NewGetIPIOCompletionProc + to set up a GetIPIOCompletionUPP universal procptr to pass as + the ioCompletion parameter. + 2. For the ipctlEchoICMP and ipctlLAPStats Control calls, use + NewIPIOCompletion to set up a IPIOCompletionUPP universal procptr + to pass in the ioCompletion field of the parameter block. + 3. For TCPCreatePB Control calls, use NewTCPNotifyProc to set up a + TCPNotifyUPP universal procptr to pass in the notifyProc field + of the parameter block + 4. For all of the TCP Control calls using the TCPiopb parameter block, + use NewTCPIOCompletionProc to set up a TCPIOCompletionUPP + universal procptr to pass in the ioCompletion field of the paramter + block. + 5. For UDBCreatePB Control calls, use NewUDPNotifyProc to set up a + UDPNotifyUPP universal procptr to pass in the notifyProc field + of the parameter block + 6. For all of the UDP Control calls using the UDPiopb parameter block, + use NewUDPIOCompletionProc to set up a UDPIOCompletionUPP + universal procptr to pass in the ioCompletion field of the paramter + block. + 7. For all calls implementing a notifyProc or ioCompletion routine + which was set up using a NewTCPRoutineProc call, do not call + DisposeRoutineSDescriptor on the universal procptr until + after the completion or notify proc has completed. +*/ +/* MacTCP return Codes in the range -23000 through -23049 */ + +enum { + inProgress = 1, /* I/O in progress */ + ipBadLapErr = -23000, /* bad network configuration */ + ipBadCnfgErr = -23001, /* bad IP configuration error */ + ipNoCnfgErr = -23002, /* missing IP or LAP configuration error */ + ipLoadErr = -23003, /* error in MacTCP load */ + ipBadAddr = -23004, /* error in getting address */ + connectionClosing = -23005, /* connection is closing */ + invalidLength = -23006, + connectionExists = -23007, /* request conflicts with existing connection */ + connectionDoesntExist = -23008, /* connection does not exist */ + insufficientResources = -23009, /* insufficient resources to perform request */ + invalidStreamPtr = -23010, + streamAlreadyOpen = -23011, + connectionTerminated = -23012, + invalidBufPtr = -23013, + invalidRDS = -23014, + invalidWDS = -23014, + openFailed = -23015, + commandTimeout = -23016, + duplicateSocket = -23017 +}; + +/* Error codes from internal IP functions */ +enum { + ipDontFragErr = -23032, /* Packet too large to send w/o fragmenting */ + ipDestDeadErr = -23033, /* destination not responding */ + icmpEchoTimeoutErr = -23035, /* ICMP echo timed-out */ + ipNoFragMemErr = -23036, /* no memory to send fragmented pkt */ + ipRouteErr = -23037, /* can't route packet off-net */ + nameSyntaxErr = -23041, + cacheFault = -23042, + noResultProc = -23043, + noNameServer = -23044, + authNameErr = -23045, + noAnsErr = -23046, + dnrErr = -23047, + outOfMemory = -23048 +}; + +enum { + BYTES_16WORD = 2, /* bytes per = 16, bit ip word */ + BYTES_32WORD = 4, /* bytes per = 32, bit ip word */ + BYTES_64WORD = 8 /* bytes per = 64, bit ip word */ +}; + +/* 8-bit quantity */ +typedef unsigned char UInt8; +typedef signed char SInt8; +typedef UInt8 b_8; + +/* 16-bit quantity */ +typedef unsigned short UInt16; +typedef signed short SInt16; +typedef UInt16 b_16; + +/* 32-bit quantity */ +typedef unsigned long UInt32; +typedef signed long SInt32; +typedef UInt32 b_32; + +/* IP address is 32-bits */ +typedef b_32 ip_addr; + +struct ip_addrbytes { + union { + b_32 addr; + UInt8 byte[4]; + } a; +}; +typedef struct ip_addrbytes ip_addrbytes; + +struct wdsEntry { + unsigned short length; /* length of buffer */ + Ptr ptr; /* pointer to buffer */ +}; +typedef struct wdsEntry wdsEntry; + +struct rdsEntry { + unsigned short length; /* length of buffer */ + Ptr ptr; /* pointer to buffer */ +}; +typedef struct rdsEntry rdsEntry; + +typedef unsigned long BufferPtr; + +typedef unsigned long StreamPtr; + + +enum { + netUnreach = 0, + hostUnreach = 1, + protocolUnreach = 2, + portUnreach = 3, + fragReqd = 4, + sourceRouteFailed = 5, + timeExceeded = 6, + parmProblem = 7, + missingOption = 8, + lastICMPMsgType = 32767 +}; + +typedef unsigned short ICMPMsgType; + +typedef b_16 ip_port; + +struct ICMPReport { + StreamPtr streamPtr; + ip_addr localHost; + ip_port localPort; + ip_addr remoteHost; + ip_port remotePort; + short reportType; + unsigned short optionalAddlInfo; + unsigned long optionalAddlInfoPtr; +}; +typedef struct ICMPReport ICMPReport; + +/* csCode to get our IP address */ + +enum { + ipctlGetAddr = 15 +}; + +typedef void (*GetIPIOCompletionProcPtr)(struct GetAddrParamBlock *iopb); + +#if GENERATINGCFM +typedef UniversalProcPtr GetIPIOCompletionUPP; +#else +typedef GetIPIOCompletionProcPtr GetIPIOCompletionUPP; +#endif + +#define GetIPParamBlockHeader \ + struct QElem* qLink; \ + short qType; \ + short ioTrap; \ + Ptr ioCmdAddr; \ + GetIPIOCompletionUPP ioCompletion; \ + OSErr ioResult; \ + StringPtr ioNamePtr; \ + short ioVRefNum; \ + short ioCRefNum; \ + short csCode +struct GetAddrParamBlock { + struct QElem *qLink; + short qType; + short ioTrap; + Ptr ioCmdAddr; + GetIPIOCompletionUPP ioCompletion; + OSErr ioResult; + StringPtr ioNamePtr; + short ioVRefNum; + short ioCRefNum; + short csCode; /* standard I/O header */ + ip_addr ourAddress; /* our IP address */ + long ourNetMask; /* our IP net mask */ +}; +typedef struct GetAddrParamBlock GetAddrParamBlock; + +/* control codes */ + +enum { + ipctlEchoICMP = 17, /* send icmp echo */ + ipctlLAPStats = 19 /* get lap stats */ +}; + +typedef void (*IPIOCompletionProcPtr)(struct ICMPParamBlock *iopb); + +#if GENERATINGCFM +typedef UniversalProcPtr IPIOCompletionUPP; +#else +typedef IPIOCompletionProcPtr IPIOCompletionUPP; +#endif + +#define IPParamBlockHeader \ + struct QElem* qLink; \ + short qType; \ + short ioTrap; \ + Ptr ioCmdAddr; \ + IPIOCompletionUPP ioCompletion; \ + OSErr ioResult; \ + StringPtr ioNamePtr; \ + short ioVRefNum; \ + short ioCRefNum; \ + short csCode +struct ICMPParamBlock { + struct QElem *qLink; + short qType; + short ioTrap; + Ptr ioCmdAddr; + IPIOCompletionUPP ioCompletion; + OSErr ioResult; + StringPtr ioNamePtr; + short ioVRefNum; + short ioCRefNum; + short csCode; /* standard I/O header */ + short params[11]; + struct { + unsigned long echoRequestOut; /* time in ticks of when the echo request went out */ + unsigned long echoReplyIn; /* time in ticks of when the reply was received */ + struct rdsEntry echoedData; /* data received in responce */ + Ptr options; + unsigned long userDataPtr; + } icmpEchoInfo; +}; +typedef pascal void (*ICMPEchoNotifyProcPtr)(struct ICMPParamBlock *iopb); + +#if GENERATINGCFM +typedef UniversalProcPtr ICMPEchoNotifyUPP; +#else +typedef ICMPEchoNotifyProcPtr ICMPEchoNotifyUPP; +#endif + +struct IPParamBlock { + struct QElem *qLink; + short qType; + short ioTrap; + Ptr ioCmdAddr; + IPIOCompletionUPP ioCompletion; + OSErr ioResult; + StringPtr ioNamePtr; + short ioVRefNum; + short ioCRefNum; + short csCode; /* standard I/O header */ + union { + struct { + ip_addr dest; /* echo to IP address */ + wdsEntry data; + short timeout; + Ptr options; + unsigned short optLength; + ICMPEchoNotifyUPP icmpCompletion; + unsigned long userDataPtr; + } IPEchoPB; + struct { + struct LAPStats *lapStatsPtr; + } LAPStatsPB; + } csParam; +}; +union LAPStatsAddrXlation { + struct arp_entry *arp_table; + struct nbp_entry *nbp_table; +}; +struct LAPStats { + short ifType; + char *ifString; + short ifMaxMTU; + long ifSpeed; + short ifPhyAddrLength; + char *ifPhysicalAddress; + union LAPStatsAddrXlation AddrXlation; + short slotNumber; +}; +typedef struct LAPStats LAPStats; + +#ifdef __APPLETALK__ +struct nbp_entry { + ip_addr ip_address; /* IP address */ + AddrBlock at_address; /* matching AppleTalk address */ + Boolean gateway; /* TRUE if entry for a gateway */ + Boolean valid; /* TRUE if LAP address is valid */ + Boolean probing; /* TRUE if NBP lookup pending */ + SInt8 afiller; /* Filler for proper byte alignment */ + long age; /* ticks since cache entry verified */ + long access; /* ticks since last access */ + SInt8 filler[116]; /* for internal use only !!! */ +}; +#endif +struct Enet_addr { + b_16 en_hi; + b_32 en_lo; +}; +typedef struct Enet_addr Enet_addr; + +struct arp_entry { + short age; /* cache aging field */ + b_16 protocol; /* Protocol type */ + ip_addr ip_address; /* IP address */ + Enet_addr en_address; /* matching Ethernet address */ +}; +typedef struct arp_entry arp_entry; + +/* number of ARP table entries */ + +enum { + ARP_TABLE_SIZE = 20 +}; + +enum { + NBP_TABLE_SIZE = 20, /* number of NBP table entries */ + NBP_MAX_NAME_SIZE = 16 + 10 + 2 +}; + +/* Command codes */ +enum { + TCPCreate = 30, + TCPPassiveOpen = 31, + TCPActiveOpen = 32, + TCPSend = 34, + TCPNoCopyRcv = 35, + TCPRcvBfrReturn = 36, + TCPRcv = 37, + TCPClose = 38, + TCPAbort = 39, + TCPStatus = 40, + TCPExtendedStat = 41, + TCPRelease = 42, + TCPGlobalInfo = 43, + TCPCtlMax = 49 +}; + +enum { + TCPClosing = 1, + TCPULPTimeout = 2, + TCPTerminate = 3, + TCPDataArrival = 4, + TCPUrgent = 5, + TCPICMPReceived = 6, + lastEvent = 32767 +}; + +typedef unsigned short TCPEventCode; + + +enum { + TCPRemoteAbort = 2, + TCPNetworkFailure = 3, + TCPSecPrecMismatch = 4, + TCPULPTimeoutTerminate = 5, + TCPULPAbort = 6, + TCPULPClose = 7, + TCPServiceError = 8, + lastReason = 32767 +}; + +typedef unsigned short TCPTerminationReason; + +typedef pascal void (*TCPNotifyProcPtr)(StreamPtr tcpStream, unsigned short eventCode, Ptr userDataPtr, unsigned short terminReason, struct ICMPReport *icmpMsg); + +#if GENERATINGCFM +typedef UniversalProcPtr TCPNotifyUPP; +#else +typedef TCPNotifyProcPtr TCPNotifyUPP; +#endif + +typedef unsigned short tcp_port; + +/* ValidityFlags */ + +enum { + timeoutValue = 0x80, + timeoutAction = 0x40, + typeOfService = 0x20, + precedence = 0x10 +}; + +/* TOSFlags */ +enum { + lowDelay = 0x01, + throughPut = 0x02, + reliability = 0x04 +}; + +struct TCPCreatePB { + Ptr rcvBuff; + unsigned long rcvBuffLen; + TCPNotifyUPP notifyProc; + Ptr userDataPtr; +}; +typedef struct TCPCreatePB TCPCreatePB; + +struct TCPOpenPB { + SInt8 ulpTimeoutValue; + SInt8 ulpTimeoutAction; + SInt8 validityFlags; + SInt8 commandTimeoutValue; + ip_addr remoteHost; + tcp_port remotePort; + ip_addr localHost; + tcp_port localPort; + SInt8 tosFlags; + SInt8 precedence; + Boolean dontFrag; + SInt8 timeToLive; + SInt8 security; + SInt8 optionCnt; + SInt8 options[40]; + Ptr userDataPtr; +}; +typedef struct TCPOpenPB TCPOpenPB; + +struct TCPSendPB { + SInt8 ulpTimeoutValue; + SInt8 ulpTimeoutAction; + SInt8 validityFlags; + Boolean pushFlag; + Boolean urgentFlag; + SInt8 filler; /* Filler for proper byte alignment */ + Ptr wdsPtr; + unsigned long sendFree; + unsigned short sendLength; + Ptr userDataPtr; +}; +typedef struct TCPSendPB TCPSendPB; + +/* for receive and return rcv buff calls */ +/* Note: the filler in the following structure is in a different location than */ +/* that specified in the Programmer's Guide. */ +struct TCPReceivePB { + SInt8 commandTimeoutValue; + Boolean markFlag; + Boolean urgentFlag; + SInt8 filler; /* Filler for proper byte alignment */ + Ptr rcvBuff; + unsigned short rcvBuffLen; + Ptr rdsPtr; + unsigned short rdsLength; + unsigned short secondTimeStamp; + Ptr userDataPtr; +}; +typedef struct TCPReceivePB TCPReceivePB; + +struct TCPClosePB { + SInt8 ulpTimeoutValue; + SInt8 ulpTimeoutAction; + SInt8 validityFlags; + SInt8 filler; /* Filler for proper byte alignment */ + Ptr userDataPtr; +}; +typedef struct TCPClosePB TCPClosePB; + +struct HistoBucket { + unsigned short value; + unsigned long counter; +}; +typedef struct HistoBucket HistoBucket; + + +enum { + NumOfHistoBuckets = 7 +}; + +struct TCPConnectionStats { + unsigned long dataPktsRcvd; + unsigned long dataPktsSent; + unsigned long dataPktsResent; + unsigned long bytesRcvd; + unsigned long bytesRcvdDup; + unsigned long bytesRcvdPastWindow; + unsigned long bytesSent; + unsigned long bytesResent; + unsigned short numHistoBuckets; + struct HistoBucket sentSizeHisto[NumOfHistoBuckets]; + unsigned short lastRTT; + unsigned short tmrSRTT; + unsigned short rttVariance; + unsigned short tmrRTO; + SInt8 sendTries; + SInt8 sourchQuenchRcvd; +}; +typedef struct TCPConnectionStats TCPConnectionStats; + +struct TCPStatusPB { + SInt8 ulpTimeoutValue; + SInt8 ulpTimeoutAction; + long unused; + ip_addr remoteHost; + tcp_port remotePort; + ip_addr localHost; + tcp_port localPort; + SInt8 tosFlags; + SInt8 precedence; + SInt8 connectionState; + SInt8 filler; /* Filler for proper byte alignment */ + unsigned short sendWindow; + unsigned short rcvWindow; + unsigned short amtUnackedData; + unsigned short amtUnreadData; + Ptr securityLevelPtr; + unsigned long sendUnacked; + unsigned long sendNext; + unsigned long congestionWindow; + unsigned long rcvNext; + unsigned long srtt; + unsigned long lastRTT; + unsigned long sendMaxSegSize; + struct TCPConnectionStats *connStatPtr; + Ptr userDataPtr; +}; +typedef struct TCPStatusPB TCPStatusPB; + +struct TCPAbortPB { + Ptr userDataPtr; +}; +typedef struct TCPAbortPB TCPAbortPB; + +struct TCPParam { + unsigned long tcpRtoA; + unsigned long tcpRtoMin; + unsigned long tcpRtoMax; + unsigned long tcpMaxSegSize; + unsigned long tcpMaxConn; + unsigned long tcpMaxWindow; +}; +typedef struct TCPParam TCPParam; + +struct TCPStats { + unsigned long tcpConnAttempts; + unsigned long tcpConnOpened; + unsigned long tcpConnAccepted; + unsigned long tcpConnClosed; + unsigned long tcpConnAborted; + unsigned long tcpOctetsIn; + unsigned long tcpOctetsOut; + unsigned long tcpOctetsInDup; + unsigned long tcpOctetsRetrans; + unsigned long tcpInputPkts; + unsigned long tcpOutputPkts; + unsigned long tcpDupPkts; + unsigned long tcpRetransPkts; +}; +typedef struct TCPStats TCPStats; + +typedef StreamPtr *StreamPPtr; + +struct TCPGlobalInfoPB { + struct TCPParam *tcpParamPtr; + struct TCPStats *tcpStatsPtr; + StreamPPtr tcpCDBTable[1]; + Ptr userDataPtr; + unsigned short maxTCPConnections; +}; +typedef struct TCPGlobalInfoPB TCPGlobalInfoPB; + +typedef void (*TCPIOCompletionProcPtr)(struct TCPiopb *iopb); + +#if GENERATINGCFM +typedef UniversalProcPtr TCPIOCompletionUPP; +#else +typedef TCPIOCompletionProcPtr TCPIOCompletionUPP; +#endif + +struct TCPiopb { + SInt8 fill12[12]; + TCPIOCompletionUPP ioCompletion; + short ioResult; + Ptr ioNamePtr; + short ioVRefNum; + short ioCRefNum; + short csCode; + StreamPtr tcpStream; + union { + struct TCPCreatePB create; + struct TCPOpenPB open; + struct TCPSendPB send; + struct TCPReceivePB receive; + struct TCPClosePB close; + struct TCPAbortPB abort; + struct TCPStatusPB status; + struct TCPGlobalInfoPB globalInfo; + } csParam; +}; +typedef struct TCPiopb TCPiopb; + + +enum { + UDPCreate = 20, + UDPRead = 21, + UDPBfrReturn = 22, + UDPWrite = 23, + UDPRelease = 24, + UDPMaxMTUSize = 25, + UDPStatus = 26, + UDPMultiCreate = 27, + UDPMultiSend = 28, + UDPMultiRead = 29, + UDPCtlMax = 29 +}; + +enum { + UDPDataArrival = 1, + UDPICMPReceived = 2, + lastUDPEvent = 32767 +}; + +typedef unsigned short UDPEventCode; + +typedef pascal void (*UDPNotifyProcPtr)(StreamPtr udpStream, unsigned short eventCode, Ptr userDataPtr, struct ICMPReport *icmpMsg); + +#if GENERATINGCFM +typedef UniversalProcPtr UDPNotifyUPP; +#else +typedef UDPNotifyProcPtr UDPNotifyUPP; +#endif + +typedef unsigned short udp_port; + +/* for create and release calls */ +struct UDPCreatePB { + Ptr rcvBuff; + unsigned long rcvBuffLen; + UDPNotifyUPP notifyProc; + unsigned short localPort; + Ptr userDataPtr; + udp_port endingPort; +}; +typedef struct UDPCreatePB UDPCreatePB; + +struct UDPSendPB { + unsigned short reserved; + ip_addr remoteHost; + udp_port remotePort; + Ptr wdsPtr; + Boolean checkSum; + SInt8 filler; /* Filler for proper byte alignment */ + unsigned short sendLength; + Ptr userDataPtr; + udp_port localPort; +}; +typedef struct UDPSendPB UDPSendPB; + +/* for receive and buffer return calls */ +struct UDPReceivePB { + unsigned short timeOut; + ip_addr remoteHost; + udp_port remotePort; + Ptr rcvBuff; + unsigned short rcvBuffLen; + unsigned short secondTimeStamp; + Ptr userDataPtr; + ip_addr destHost; /* only for use with multi rcv */ + udp_port destPort; /* only for use with multi rcv */ +}; +typedef struct UDPReceivePB UDPReceivePB; + +struct UDPMTUPB { + unsigned short mtuSize; + ip_addr remoteHost; + Ptr userDataPtr; +}; +typedef struct UDPMTUPB UDPMTUPB; + +typedef void (*UDPIOCompletionProcPtr)(struct UDPiopb *iopb); + +#if GENERATINGCFM +typedef UniversalProcPtr UDPIOCompletionUPP; +#else +typedef UDPIOCompletionProcPtr UDPIOCompletionUPP; +#endif + +struct UDPiopb { + SInt8 fill12[12]; + UDPIOCompletionUPP ioCompletion; + short ioResult; + Ptr ioNamePtr; + short ioVRefNum; + short ioCRefNum; + short csCode; + StreamPtr udpStream; + union { + struct UDPCreatePB create; + struct UDPSendPB send; + struct UDPReceivePB receive; + struct UDPMTUPB mtu; + } csParam; +}; +typedef struct UDPiopb UDPiopb; + +enum { + uppGetIPIOCompletionProcInfo = kCStackBased + | STACK_ROUTINE_PARAMETER(1, SIZE_CODE(sizeof(struct GetAddrParamBlock*))), + uppIPIOCompletionProcInfo = kCStackBased + | STACK_ROUTINE_PARAMETER(1, SIZE_CODE(sizeof(struct ICMPParamBlock*))), + uppICMPEchoNotifyProcInfo = kPascalStackBased + | STACK_ROUTINE_PARAMETER(1, SIZE_CODE(sizeof(struct ICMPParamBlock*))), + uppTCPNotifyProcInfo = kPascalStackBased + | STACK_ROUTINE_PARAMETER(1, SIZE_CODE(sizeof(StreamPtr))) + | STACK_ROUTINE_PARAMETER(2, SIZE_CODE(sizeof(unsigned short))) + | STACK_ROUTINE_PARAMETER(3, SIZE_CODE(sizeof(Ptr))) + | STACK_ROUTINE_PARAMETER(4, SIZE_CODE(sizeof(unsigned short))) + | STACK_ROUTINE_PARAMETER(5, SIZE_CODE(sizeof(struct ICMPReport*))), + uppTCPIOCompletionProcInfo = kCStackBased + | STACK_ROUTINE_PARAMETER(1, SIZE_CODE(sizeof(struct TCPiopb*))), + uppUDPNotifyProcInfo = kPascalStackBased + | STACK_ROUTINE_PARAMETER(1, SIZE_CODE(sizeof(StreamPtr))) + | STACK_ROUTINE_PARAMETER(2, SIZE_CODE(sizeof(unsigned short))) + | STACK_ROUTINE_PARAMETER(3, SIZE_CODE(sizeof(Ptr))) + | STACK_ROUTINE_PARAMETER(4, SIZE_CODE(sizeof(struct ICMPReport*))), + uppUDPIOCompletionProcInfo = kCStackBased + | STACK_ROUTINE_PARAMETER(1, SIZE_CODE(sizeof(struct UDPiopb*))) +}; + +#if GENERATINGCFM +#define NewGetIPIOCompletionProc(userRoutine) \ + (GetIPIOCompletionUPP) NewRoutineDescriptor((ProcPtr)(userRoutine), uppGetIPIOCompletionProcInfo, GetCurrentArchitecture()) +#define NewIPIOCompletionProc(userRoutine) \ + (IPIOCompletionUPP) NewRoutineDescriptor((ProcPtr)(userRoutine), uppIPIOCompletionProcInfo, GetCurrentArchitecture()) +#define NewICMPEchoNotifyProc(userRoutine) \ + (ICMPEchoNotifyUPP) NewRoutineDescriptor((ProcPtr)(userRoutine), uppICMPEchoNotifyProcInfo, GetCurrentArchitecture()) +#define NewTCPNotifyProc(userRoutine) \ + (TCPNotifyUPP) NewRoutineDescriptor((ProcPtr)(userRoutine), uppTCPNotifyProcInfo, GetCurrentArchitecture()) +#define NewTCPIOCompletionProc(userRoutine) \ + (TCPIOCompletionUPP) NewRoutineDescriptor((ProcPtr)(userRoutine), uppTCPIOCompletionProcInfo, GetCurrentArchitecture()) +#define NewUDPNotifyProc(userRoutine) \ + (UDPNotifyUPP) NewRoutineDescriptor((ProcPtr)(userRoutine), uppUDPNotifyProcInfo, GetCurrentArchitecture()) +#define NewUDPIOCompletionProc(userRoutine) \ + (UDPIOCompletionUPP) NewRoutineDescriptor((ProcPtr)(userRoutine), uppUDPIOCompletionProcInfo, GetCurrentArchitecture()) +#else +#define NewGetIPIOCompletionProc(userRoutine) \ + ((GetIPIOCompletionUPP) (userRoutine)) +#define NewIPIOCompletionProc(userRoutine) \ + ((IPIOCompletionUPP) (userRoutine)) +#define NewICMPEchoNotifyProc(userRoutine) \ + ((ICMPEchoNotifyUPP) (userRoutine)) +#define NewTCPNotifyProc(userRoutine) \ + ((TCPNotifyUPP) (userRoutine)) +#define NewTCPIOCompletionProc(userRoutine) \ + ((TCPIOCompletionUPP) (userRoutine)) +#define NewUDPNotifyProc(userRoutine) \ + ((UDPNotifyUPP) (userRoutine)) +#define NewUDPIOCompletionProc(userRoutine) \ + ((UDPIOCompletionUPP) (userRoutine)) +#endif + +#if GENERATINGCFM +#define CallGetIPIOCompletionProc(userRoutine, iopb) \ + CallUniversalProc((UniversalProcPtr)(userRoutine), uppGetIPIOCompletionProcInfo, (iopb)) +#define CallIPIOCompletionProc(userRoutine, iopb) \ + CallUniversalProc((UniversalProcPtr)(userRoutine), uppIPIOCompletionProcInfo, (iopb)) +#define CallICMPEchoNotifyProc(userRoutine, iopb) \ + CallUniversalProc((UniversalProcPtr)(userRoutine), uppICMPEchoNotifyProcInfo, (iopb)) +#define CallTCPNotifyProc(userRoutine, tcpStream, eventCode, userDataPtr, terminReason, icmpMsg) \ + CallUniversalProc((UniversalProcPtr)(userRoutine), uppTCPNotifyProcInfo, (tcpStream), (eventCode), (userDataPtr), (terminReason), (icmpMsg)) +#define CallTCPIOCompletionProc(userRoutine, iopb) \ + CallUniversalProc((UniversalProcPtr)(userRoutine), uppTCPIOCompletionProcInfo, (iopb)) +#define CallUDPNotifyProc(userRoutine, udpStream, eventCode, userDataPtr, icmpMsg) \ + CallUniversalProc((UniversalProcPtr)(userRoutine), uppUDPNotifyProcInfo, (udpStream), (eventCode), (userDataPtr), (icmpMsg)) +#define CallUDPIOCompletionProc(userRoutine, iopb) \ + CallUniversalProc((UniversalProcPtr)(userRoutine), uppUDPIOCompletionProcInfo, (iopb)) +#else +#define CallGetIPIOCompletionProc(userRoutine, iopb) \ + (*(userRoutine))((iopb)) +#define CallIPIOCompletionProc(userRoutine, iopb) \ + (*(userRoutine))((iopb)) +#define CallICMPEchoNotifyProc(userRoutine, iopb) \ + (*(userRoutine))((iopb)) +#define CallTCPNotifyProc(userRoutine, tcpStream, eventCode, userDataPtr, terminReason, icmpMsg) \ + (*(userRoutine))((tcpStream), (eventCode), (userDataPtr), (terminReason), (icmpMsg)) +#define CallTCPIOCompletionProc(userRoutine, iopb) \ + (*(userRoutine))((iopb)) +#define CallUDPNotifyProc(userRoutine, udpStream, eventCode, userDataPtr, icmpMsg) \ + (*(userRoutine))((udpStream), (eventCode), (userDataPtr), (icmpMsg)) +#define CallUDPIOCompletionProc(userRoutine, iopb) \ + (*(userRoutine))((iopb)) +#endif + + +#if PRAGMA_IMPORT_SUPPORTED +#pragma import off +#endif + +#if PRAGMA_ALIGN_SUPPORTED +#pragma options align=reset +#endif + +#ifdef __cplusplus +} +#endif + +#endif /* __MACTCP__ */ --- main.c Sun Jan 30 14:13:53 2022 +++ main.c Sun Jan 30 14:13:53 2022 @@ -0,0 +1,341 @@ +/* + * Copyright (c) 2021-2022 joshua stein <jcs@jcs.org> + * + * Permission to use, copy, modify, and 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. + */ + +#include <string.h> +#include "chatter.h" +#include "irc.h" +#include "tcp.h" +#include "util.h" + +#define MBAR_ID 128 +#define APPLE_MENU_ID 128 +#define APPLE_MENU_ABOUT_ID 1 +#define FILE_MENU_ID 129 +#define FILE_MENU_CONNECT_ID 1 +#define FILE_MENU_QUIT_ID 3 +#define EDIT_MENU_ID 130 + +#define SOCKS_PROXY_IP_ID 1005 +#define SOCKS_PROXY_PORT_ID 1006 + +#define CONNECT_DLOG_ID 129 +#define CONNECT_SERVER_ID 2 +#define CONNECT_PORT_ID 3 +#define CONNECT_NICK_ID 4 + +MenuHandle apple_menu, file_menu; +struct focusable *cur_focusable = NULL; +struct focusable *last_focusable = NULL; +ip_addr socks_proxy_ip = 0; +ip_port socks_proxy_port = 0; + +void update_menu(void); +void handle_exit(void); +short handle_menu(long menu_id); +short show_connect_dialog(void); + +int +main(void) +{ + Handle mbar; + EventRecord event; + WindowPtr event_win; + GrafPtr old_port; + AppFile finder_file; + short event_in, n; + char key, *socks_proxy_host; + + InitGraf(&thePort); + InitFonts(); + FlushEvents(everyEvent, 0); + InitWindows(); + InitMenus(); + TEInit(); + InitDialogs(0); + InitCursor(); + MaxApplZone(); + + _atexit(handle_exit); + + if (!(mbar = GetNewMBar(MBAR_ID))) + panic("no mbar"); + SetMenuBar(mbar); + if (!(apple_menu = GetMHandle(APPLE_MENU_ID))) + panic("no apple menu"); + AddResMenu(apple_menu, 'DRVR'); + if (!(file_menu = GetMHandle(FILE_MENU_ID))) + panic("no file menu"); + update_menu(); + DrawMenuBar(); + + socks_proxy_host = xGetStringAsChar(SOCKS_PROXY_IP_ID); + if (socks_proxy_host[0] != '\0') + socks_proxy_ip = ip2long(socks_proxy_host); + free(socks_proxy_host); + socks_proxy_port = (ip_port)xGetStringAsLong(SOCKS_PROXY_PORT_ID); + +chatter_init("irc.libera.chat", 6667, "jcs[mac]"); + + for (;;) { + WaitNextEvent(everyEvent, &event, 5L, 0L); + + switch (event.what) { + case nullEvent: + /* TODO: idle all chatters, not just current */ + if (cur_focusable && cur_focusable->idle) + cur_focusable->idle(cur_focusable, &event); + break; + case keyDown: + case autoKey: + key = event.message & charCodeMask; + if ((event.modifiers & cmdKey) != 0 && + handle_menu(MenuKey(key)) == 1) + break; + if (cur_focusable && cur_focusable->key_down) + cur_focusable->key_down(cur_focusable, &event); + break; + case mouseDown: + event_in = FindWindow(event.where, &event_win); + + switch (event_in) { + case inMenuBar: + handle_menu(MenuSelect(event.where)); + break; + case inSysWindow: + SystemClick(&event, event_win); + break; + case inDrag: + DragWindow(event_win, event.where, &screenBits.bounds); + break; + case inGoAway: + if (TrackGoAway(event_win, event.where) && cur_focusable && + cur_focusable->close) + cur_focusable->close(cur_focusable, &event); + break; + case inContent: + if (event_win != FrontWindow()) { + SelectWindow(event_win); + if (last_focusable && + last_focusable->win == event_win) { + struct focusable *fcur; + fcur = cur_focusable; + cur_focusable = last_focusable; + last_focusable = fcur; + } + } + if (cur_focusable && cur_focusable->win == event_win && + cur_focusable->mouse_down) + cur_focusable->mouse_down(cur_focusable, &event); + break; + } + break; + case updateEvt: + case activateEvt: + event_win = (WindowPtr)event.message; + + if (event.what == updateEvt) { + GetPort(&old_port); + SetPort(event_win); + BeginUpdate(event_win); + } + + if (cur_focusable && cur_focusable->update) + cur_focusable->update(cur_focusable, &event); + + if (event.what == updateEvt) { + EndUpdate(event_win); + SetPort(old_port); + } + break; + case app4Evt: + if (HiWord(event.message) & (1 << 8)) { + /* multifinder suspend/resume */ + switch (event.message & (1 << 0)) { + case 0: + /* suspend */ + if (cur_focusable && cur_focusable->suspend) + cur_focusable->suspend(cur_focusable, &event); + break; + case 1: + /* resume */ + if (cur_focusable && cur_focusable->resume) + cur_focusable->resume(cur_focusable, &event); + break; + } + } + break; + } + } + + return 0; +} + +short +show_connect_dialog(void) +{ + Str255 server, nick, ports; + DialogPtr dlg; + bool done = false, connect = false; + short hit, itype; + long port; + Handle ihandle; + Rect irect; + + if ((dlg = GetNewDialog(CONNECT_DLOG_ID, nil, (WindowPtr)-1)) == NULL) + panic("Can't find connection DLOG %d", CONNECT_DLOG_ID); + + ShowWindow(dlg); + + while (!done) { +get_input: + ModalDialog(ModalDialogFilter, &hit); + switch (hit) { + case -1: + done = true; + break; + case OK: + done = true; + connect = true; + break; + } + + if (connect) { + GetDItem(dlg, CONNECT_SERVER_ID, &itype, &ihandle, &irect); + GetIText(ihandle, server); + PtoCstr(server); + if (server[0] == '\0') { + warn("Server name cannot be blank"); + connect = false; + continue; + } + + GetDItem(dlg, CONNECT_NICK_ID, &itype, &ihandle, &irect); + GetIText(ihandle, nick); + PtoCstr(nick); + if (nick[0] == '\0') { + warn("Nick cannot be blank"); + connect = false; + continue; + } + + GetDItem(dlg, CONNECT_PORT_ID, &itype, &ihandle, &irect); + GetIText(ihandle, ports); + PtoCstr(ports); + if (sscanf((char *)ports, "%d", &port) != 1 || + port < 1 || port > 65535) { + warn("Port must be between 1-65535"); + connect = false; + continue; + } + + break; + } + } + + DisposeDialog(dlg); + + if (connect) + chatter_init((char *)&server, port, (char *)&nick); +} + +short +handle_menu(long menu_id) +{ + short ret = 0; + + switch (HiWord(menu_id)) { + case APPLE_MENU_ID: + switch (LoWord(menu_id)) { + case APPLE_MENU_ABOUT_ID: { + VersRecHndl vers; + char vers_s[255]; + char short_vers[255] = { 0 }; + short vlen; + + if ((vers = (VersRecHndl)GetResource('vers', 1))) { + /* + * vers "long version string" is a pascal string after the + * short version pascal string + */ + HLock(vers); + vlen = (*vers)->shortVersion[0]; + memcpy(short_vers, (*vers)->shortVersion + vlen + 1, + sizeof((*vers)->shortVersion) - vlen - 1); + PtoCstr(short_vers); + sprintf(vers_s, "%s %s", PROGRAM_NAME, short_vers); + ReleaseResource(vers); + note("%s", vers_s); + } else + warnx("Can't find version number!"); + + ret = 1; + break; + } + } + break; + case FILE_MENU_ID: + switch (LoWord(menu_id)) { + case FILE_MENU_CONNECT_ID: + show_connect_dialog(); + break; + case FILE_MENU_QUIT_ID: + exit(0); + } + break; + case EDIT_MENU_ID: + /* let each focusable handle this */ + break; + } + + HiliteMenu(0); + return ret; +} + +void +update_menu(void) +{ +} + +void +handle_exit(void) +{ + /* TODO: close all chatters, not just current */ + if (cur_focusable) + cur_focusable->atexit(cur_focusable); +} + +void +show_focusable(struct focusable *focusable) +{ + if (cur_focusable) + last_focusable = cur_focusable; + cur_focusable = focusable; + + ShowWindow(focusable->win); +} + +void +close_focusable(struct focusable *focusable) +{ + if (cur_focusable == focusable) { + cur_focusable = NULL; + if (last_focusable) + cur_focusable = last_focusable; + } else if (last_focusable == focusable) + last_focusable = NULL; + + CloseWindow(focusable->win); +} --- tcp.c Tue Oct 5 00:19:37 2021 +++ tcp.c Tue Oct 5 00:19:37 2021 @@ -0,0 +1,528 @@ +#include <stdio.h> +#include <string.h> +#include "tcp.h" +#include "util.h" + +#define RCV_BUFFER_SIZE 1024 +#define TCP_BUFFER_SIZE 8192 +#define OPEN_TIMEOUT 60 + +short gIPPDriverRefNum; + +pascal void StrToAddrMarkDone(struct hostInfo *hi, char *data); + +unsigned long resolved_ip; + +OSErr +_TCPInit(void) +{ + ParamBlockRec pb; + OSErr osErr; + + memset(&pb, 0, sizeof(pb)); + + gIPPDriverRefNum = -1; + + pb.ioParam.ioCompletion = 0; + pb.ioParam.ioNamePtr = "\p.IPP"; + pb.ioParam.ioPermssn = fsCurPerm; + + osErr = PBOpen(&pb, false); + if (noErr == osErr) { + gIPPDriverRefNum = pb.ioParam.ioRefNum; + } + + return osErr; +} + +OSErr +_TCPGetOurIP(ip_addr *ip, long *netMask) +{ + OSErr osErr; + GetAddrParamBlock pb; + int i; + + memset(&pb, 0, sizeof(pb)); + + pb.csCode = ipctlGetAddr; + pb.ioCRefNum = gIPPDriverRefNum; + + osErr = PBControl((ParmBlkPtr)&pb, true); + while (pb.ioResult > 0) + ; + + if (pb.ioResult != noErr) + return pb.ioResult; + + if (ip != NULL) + *ip = pb.ourAddress; + if (netMask != NULL) + *netMask = pb.ourNetMask; + + return osErr; +} + +OSErr +_TCPCreate(TCPiopb *pb, StreamPtr *stream, Ptr rcvBufPtr, long rcvBufLen, + TCPNotifyProc aNotifyProc, Ptr userDataPtr, + TCPIOCompletionProc ioCompletion, Boolean async) +{ + OSErr osErr; + + memset(pb, 0, sizeof(*pb)); + + pb->ioCompletion = ioCompletion; + pb->ioCRefNum = gIPPDriverRefNum; + pb->csCode = TCPCreate; + + pb->csParam.create.rcvBuff = rcvBufPtr; + pb->csParam.create.rcvBuffLen = rcvBufLen; + pb->csParam.create.notifyProc = aNotifyProc; + pb->csParam.create.userDataPtr = userDataPtr; + + osErr = PBControl((ParmBlkPtr)pb, async); + if (!async && (noErr == osErr)) { + *stream = pb->tcpStream; + } + + return osErr; +} + +/* listen for an incoming connection */ +OSErr +_TCPPassiveOpen(TCPiopb *pb, StreamPtr stream, ip_addr *remoteIP, + tcp_port *remotePort, ip_addr *localIP, tcp_port *localPort, + Ptr userData, TCPIOCompletionProc ioCompletion, Boolean async) +{ + OSErr osErr; + short index; + + memset(pb, 0, sizeof(*pb)); + + pb->ioCompletion = ioCompletion; + pb->ioCRefNum = gIPPDriverRefNum; + pb->csCode = TCPPassiveOpen; + pb->tcpStream = stream; + + pb->csParam.open.ulpTimeoutValue = 0; + pb->csParam.open.ulpTimeoutAction = 1; + pb->csParam.open.validityFlags = 0xC0; + pb->csParam.open.commandTimeoutValue = 30; + pb->csParam.open.remoteHost = *remoteIP; + pb->csParam.open.remotePort = *remotePort; + pb->csParam.open.localHost = 0; + pb->csParam.open.localPort = *localPort; + pb->csParam.open.tosFlags = 0; + pb->csParam.open.precedence = 0; + pb->csParam.open.dontFrag = 0; + pb->csParam.open.timeToLive = 0; + pb->csParam.open.security = 0; + pb->csParam.open.optionCnt = 0; + for (index = 0; index < sizeof(pb->csParam.open.options); ++index) + pb->csParam.open.options[index] = 0; + pb->csParam.open.userDataPtr = userData; + + osErr = PBControl((ParmBlkPtr) pb, async); + if (!async && (noErr == osErr)) { + *remoteIP = pb->csParam.open.remoteHost; + *remotePort = pb->csParam.open.remotePort; + *localIP = pb->csParam.open.localHost; + *localPort = pb->csParam.open.localPort; + } + + return osErr; +} + +/* make an outgoing connection */ +OSErr +_TCPActiveOpen(TCPiopb *pb, StreamPtr stream, ip_addr remoteIP, + tcp_port remotePort, ip_addr *localIP, tcp_port *localPort, + Ptr userData, TCPIOCompletionProc ioCompletion, Boolean async) +{ + OSErr osErr; + short index; + + memset(pb, 0, sizeof(*pb)); + + pb->ioCompletion = ioCompletion; + pb->ioCRefNum = gIPPDriverRefNum; + pb->csCode = TCPActiveOpen; + pb->tcpStream = stream; + + pb->csParam.open.ulpTimeoutValue = 30; + pb->csParam.open.ulpTimeoutAction = 1; + pb->csParam.open.validityFlags = 0xC0; +#if 0 + /* not available with this csCode */ + pb->csParam.open.commandTimeoutValue = 30; +#endif + pb->csParam.open.remoteHost = remoteIP; + pb->csParam.open.remotePort = remotePort; + pb->csParam.open.localHost = 0; + pb->csParam.open.localPort = *localPort; + pb->csParam.open.tosFlags = 0; + pb->csParam.open.precedence = 0; + pb->csParam.open.dontFrag = 0; + pb->csParam.open.timeToLive = 0; + pb->csParam.open.security = 0; + pb->csParam.open.optionCnt = 0; + for (index = 0; index < sizeof(pb->csParam.open.options); ++index) + pb->csParam.open.options[index] = 0; + pb->csParam.open.userDataPtr = userData; + + osErr = PBControl((ParmBlkPtr) pb, async); + if (!async && (noErr == osErr)) { + *localIP = pb->csParam.open.localHost; + *localPort = pb->csParam.open.localPort; + } + + return osErr; +} + +OSErr +_TCPSend(TCPiopb *pb, StreamPtr stream, wdsEntry *wdsPtr, Ptr userData, + TCPIOCompletionProc ioCompletion, Boolean async) +{ + memset(pb, 0, sizeof(*pb)); + + pb->ioCompletion = ioCompletion; + pb->ioCRefNum = gIPPDriverRefNum; + pb->csCode = TCPSend; + pb->tcpStream = stream; + + pb->csParam.send.ulpTimeoutValue = 30; + pb->csParam.send.ulpTimeoutAction = 1; + pb->csParam.send.validityFlags = 0xC0; + pb->csParam.send.pushFlag = 1; /* XXX */ + pb->csParam.send.urgentFlag = 0; + pb->csParam.send.wdsPtr = (Ptr)wdsPtr; + pb->csParam.send.sendFree = 0; + pb->csParam.send.sendLength = 0; + pb->csParam.send.userDataPtr = userData; + + return PBControl((ParmBlkPtr)pb, async); +} + +OSErr +_TCPNoCopyRcv(TCPiopb *pb, StreamPtr stream, Ptr rdsPtr, + unsigned short rdsLength, Ptr userData, TCPIOCompletionProc ioCompletion, + Boolean async) +{ + OSErr osErr; + + memset(pb, 0, sizeof(*pb)); + + pb->ioCompletion = ioCompletion; + pb->ioCRefNum = gIPPDriverRefNum; + pb->csCode = TCPNoCopyRcv; + pb->tcpStream = stream; + + pb->csParam.receive.commandTimeoutValue = 30; + pb->csParam.receive.urgentFlag = 0; + pb->csParam.receive.markFlag = 0; + pb->csParam.receive.rdsPtr = rdsPtr; + pb->csParam.receive.rdsLength = rdsLength; + pb->csParam.receive.userDataPtr = userData; + + return PBControl((ParmBlkPtr)pb, async); +} + +OSErr +_TCPRcv(TCPiopb *pb, StreamPtr stream, Ptr rcvBufPtr, + unsigned short *rcvBufLen, Ptr userData, TCPIOCompletionProc ioCompletion, + Boolean async) +{ + OSErr osErr; + + memset(pb, 0, sizeof(*pb)); + + pb->ioCompletion = ioCompletion; + pb->ioCRefNum = gIPPDriverRefNum; + pb->csCode = TCPRcv; + pb->tcpStream = stream; + + pb->csParam.receive.commandTimeoutValue = 30; + pb->csParam.receive.urgentFlag = 0; + pb->csParam.receive.markFlag = 0; + pb->csParam.receive.rcvBuff = rcvBufPtr; + pb->csParam.receive.rcvBuffLen = *rcvBufLen; + pb->csParam.receive.userDataPtr = userData; + + osErr = PBControl((ParmBlkPtr)pb, async); + if (!async) + *rcvBufLen = pb->csParam.receive.rcvBuffLen; + + return osErr; +} + +OSErr +_TCPBfrReturn(TCPiopb *pb, StreamPtr stream, Ptr rdsPtr, Ptr userData, + TCPIOCompletionProc ioCompletion, Boolean async) +{ + memset(pb, 0, sizeof(*pb)); + + pb->ioCompletion = ioCompletion; + pb->ioCRefNum = gIPPDriverRefNum; + pb->csCode = TCPRcvBfrReturn; + pb->tcpStream = stream; + + pb->csParam.receive.rdsPtr = rdsPtr; + pb->csParam.receive.userDataPtr = userData; + + return PBControl((ParmBlkPtr)pb, async); +} + +OSErr +_TCPClose(TCPiopb *pb, StreamPtr stream, Ptr userData, + TCPIOCompletionProc ioCompletion, Boolean async) +{ + memset(pb, 0, sizeof(*pb)); + + pb->ioCompletion = ioCompletion; + pb->ioCRefNum = gIPPDriverRefNum; + pb->csCode = TCPClose; + pb->tcpStream = stream; + + pb->csParam.close.ulpTimeoutValue = 30; + pb->csParam.close.ulpTimeoutAction = 1; + pb->csParam.close.validityFlags = 0xC0; + pb->csParam.close.userDataPtr = userData; + + return PBControl((ParmBlkPtr)pb, async); +} + +OSErr +_TCPAbort(TCPiopb *pb, StreamPtr stream, Ptr userData, + TCPIOCompletionProc ioCompletion, Boolean async) +{ + memset(pb, 0, sizeof(*pb)); + + pb->ioCompletion = ioCompletion; + pb->ioCRefNum = gIPPDriverRefNum; + pb->csCode = TCPAbort; + pb->tcpStream = stream; + + pb->csParam.abort.userDataPtr = userData; + + return PBControl((ParmBlkPtr)pb, async); +} + +OSErr +_TCPStatus(TCPiopb *pb, StreamPtr stream, struct TCPStatusPB *status, + Ptr userData, TCPIOCompletionProc ioCompletion, Boolean async) +{ + OSErr osErr; + + memset(pb, 0, sizeof(*pb)); + + pb->ioCompletion = ioCompletion; + pb->ioCRefNum = gIPPDriverRefNum; + pb->csCode = TCPStatus; + pb->tcpStream = stream; + pb->csParam.status.userDataPtr = userData; + + osErr = PBControl((ParmBlkPtr)pb, async); + if (!async && (noErr == osErr)) { + *status = pb->csParam.status; + } + + return osErr; +} + +OSErr +_TCPRelease(TCPiopb *pb, StreamPtr stream, Ptr userData, + TCPIOCompletionProc ioCompletion, Boolean async) +{ + OSErr osErr; + + memset(pb, 0, sizeof(*pb)); + + pb->ioCompletion = ioCompletion; + pb->ioCRefNum = gIPPDriverRefNum; + pb->csCode = TCPRelease; + pb->tcpStream = stream; + + pb->csParam.status.userDataPtr = userData; + + osErr = PBControl((ParmBlkPtr)pb, async); + + return osErr; +} + +/* convenience functions */ + +static pascal void +StrToAddrMarkDone(struct hostInfo *hi, char *data) +{ + volatile int *done = (int *)data; + *done = 1; +} + +OSErr +TCPResolveName(char **name, unsigned long *ipAddress) +{ + OSErr osErr; + struct hostInfo aHostInfo; + volatile int done = 0; + + osErr = OpenResolver(nil); + if (osErr) + return osErr; + + osErr = StrToAddr(*name, &aHostInfo, (ResultProcPtr)StrToAddrMarkDone, + (char *)&done); + + if (osErr == cacheFault) { + /* StrToAddrMarkDone will set done when DNS resolution finishes */ + while (!done) + ; + } + + if ((aHostInfo.rtnCode == noErr) || (aHostInfo.rtnCode == cacheFault)) { + /* use the first IP address for this host */ + *ipAddress = aHostInfo.addr[0]; + osErr = noErr; + } else + osErr = aHostInfo.rtnCode; + + CloseResolver(); + return osErr; +} + +long +ip2long(char *ip) +{ + long address = 0; + int dotcount = 0, i; + unsigned int b = 0; + + for (i = 0; ip[i] != 0; i++) { + if (ip[i] == '.') { + if (++dotcount > 3) + return (0); + address <<= 8; + address |= b; + b = 0; + } else if (ip[i] >= '0' && ip[i] <= '9') { + b *= 10; + b += (ip[i] - '0'); + if (b > 255) + return (0); + } else + return (0); + } + + if (dotcount != 3) + return (0); + address <<= 8; + address |= b; + return address; +} + +void +long2ip(unsigned long num, char *ip) +{ + unsigned char *tmp = (unsigned char *)&num; + (void)sprintf(ip, (const char *)"%d.%d.%d.%d", tmp[0], tmp[1], tmp[2], tmp[3]); +} + +#define SOCKS_VERSION_SOCKS5 0x5 +#define SOCKS_METHOD_AUTH_NONE 0x0 +#define SOCKS_REQUEST_CONNECT 0x1 +#define SOCKS_REQUEST_ATYP_DOMAINNAME 0x3 +#define SOCKS_REPLY_SUCCESS 0x0 + +OSErr +SOCKS5TCPActiveOpen(TCPiopb *pb, StreamPtr stream, ip_addr socks_ip, + tcp_port socks_port, char *remote_host, tcp_port remote_port, + ip_addr *local_ip, tcp_port *local_port, Ptr user_data, + TCPIOCompletionProc io_completion, Boolean async) +{ + OSErr err; + TCPStatusPB status_pb; + wdsEntry wds[2]; + char data[255] = { 0 }; + unsigned short len, remote_host_len; + + remote_host_len = strlen(remote_host); + if (remote_host_len + 7 > sizeof(data)) + return -1; + + err = _TCPActiveOpen(pb, stream, socks_ip, socks_port, local_ip, + local_port, user_data, io_completion, async); + if (err != noErr) + return err; + + data[0] = SOCKS_VERSION_SOCKS5; + data[1] = 1; /* nmethods */ + data[2] = SOCKS_METHOD_AUTH_NONE; + + memset(&wds, 0, sizeof(wds)); + wds[0].ptr = (Ptr)&data; + wds[0].length = 3; + + err = _TCPSend(pb, stream, wds, nil, nil, false); + if (err) + goto fail; + + for (;;) { + err = _TCPStatus(pb, stream, &status_pb, nil, nil, false); + if (err != noErr) + goto fail; + + if (status_pb.amtUnreadData >= 2) + break; + } + + len = 2; + err = _TCPRcv(pb, stream, (Ptr)&data, &len, nil, nil, false); + if (err != noErr) + goto fail; + + if (data[0] != SOCKS_VERSION_SOCKS5 || data[1] != SOCKS_METHOD_AUTH_NONE) + goto fail; + + len = 0; + data[len++] = SOCKS_VERSION_SOCKS5; + data[len++] = SOCKS_REQUEST_CONNECT; + data[len++] = 0; /* reserved */ + data[len++] = SOCKS_REQUEST_ATYP_DOMAINNAME; + data[len++] = remote_host_len; + memcpy(data + len, remote_host, remote_host_len); + len += remote_host_len; + data[len++] = (remote_port >> 8); + data[len++] = (remote_port & 0xff); + + memset(&wds, 0, sizeof(wds)); + wds[0].ptr = (Ptr)&data; + wds[0].length = len; + + err = _TCPSend(pb, stream, wds, nil, nil, false); + if (err) + goto fail; + + for (;;) { + err = _TCPStatus(pb, stream, &status_pb, nil, nil, false); + if (err != noErr) + goto fail; + + if (status_pb.amtUnreadData >= 7) + break; + } + + len = status_pb.amtUnreadData; + if (len > sizeof(data)) + len = sizeof(data); + err = _TCPRcv(pb, stream, (Ptr)&data, &len, nil, nil, false); + if (err != noErr) + goto fail; + + if (data[0] != SOCKS_VERSION_SOCKS5 || data[1] != SOCKS_REPLY_SUCCESS) + goto fail; + + return noErr; + +fail: + _TCPClose(pb, stream, nil, nil, false); + return err; +} --- tcp.h Fri Nov 13 14:05:09 2020 +++ tcp.h Fri Nov 13 14:05:09 2020 @@ -0,0 +1,60 @@ +/* + * Copyright (c) 1990-1992 by the University of Illinois Board of Trustees + */ + +#include "MacTCP.h" +#include "AddressXlation.h" + +#ifndef __TCP_H__ +#define __TCP_H__ + +typedef struct +{ + Handle next; + struct hostInfo hi; +} HostInfoQ, *HostInfoQPtr, **HostInfoQHandle; + +typedef ProcPtr TCPNotifyProc; +typedef void (*TCPIOCompletionProc)(struct TCPiopb *iopb); + +OSErr _TCPInit(void); +OSErr _TCPGetOurIP(ip_addr *ip, long *netMask); +OSErr _TCPCreate(TCPiopb *pb, StreamPtr *stream, Ptr rcvBufPtr, + long rcvBufLen, TCPNotifyProc aNotifyProc, Ptr userDataPtr, + TCPIOCompletionProc ioCompletion, Boolean async); +OSErr _TCPPassiveOpen(TCPiopb *pb, StreamPtr stream, ip_addr *remoteIP, + tcp_port *remotePort, ip_addr *localIP, tcp_port *localPort, + Ptr userData, TCPIOCompletionProc ioCompletion, Boolean async); +OSErr _TCPActiveOpen(TCPiopb *pb, StreamPtr stream, ip_addr remoteIP, + tcp_port remotePort, ip_addr *localIP, tcp_port *localPort, + Ptr userData, TCPIOCompletionProc ioCompletion, Boolean async); +OSErr _TCPSend(TCPiopb *pb, StreamPtr stream, wdsEntry *wdsPtr, + Ptr userData, TCPIOCompletionProc ioCompletion, Boolean async); +OSErr _TCPNoCopyRcv(TCPiopb *pb, StreamPtr stream, Ptr rdsPtr, + unsigned short rdsLength, Ptr userData, TCPIOCompletionProc ioCompletion, + Boolean async); +OSErr _TCPRcv(TCPiopb *pb, StreamPtr stream, Ptr rcvBufPtr, + unsigned short *rcvBufLen, Ptr userData, TCPIOCompletionProc ioCompletion, + Boolean async); +OSErr _TCPBfrReturn(TCPiopb *pb, StreamPtr stream, Ptr rdsPtr, Ptr userData, + TCPIOCompletionProc ioCompletion, Boolean async); +OSErr _TCPClose(TCPiopb *pb, StreamPtr stream, Ptr userData, + TCPIOCompletionProc ioCompletion, Boolean async); +OSErr _TCPAbort(TCPiopb *pb, StreamPtr stream, Ptr userData, + TCPIOCompletionProc ioCompletion, Boolean async); +OSErr _TCPStatus(TCPiopb *pb, StreamPtr stream, struct TCPStatusPB *status, + Ptr userData, TCPIOCompletionProc ioCompletion, Boolean async); +OSErr _TCPRelease(TCPiopb *pb, StreamPtr stream, Ptr userData, + TCPIOCompletionProc ioCompletion, Boolean async); + +OSErr TCPResolveName(char **name, unsigned long *ipAddress); + +long ip2long(char *ip); +void long2ip(unsigned long num, char *ip); + +OSErr SOCKS5TCPActiveOpen(TCPiopb *pb, StreamPtr stream, ip_addr socks_ip, + tcp_port socks_port, char *remote_host, tcp_port remote_port, + ip_addr *local_ip, tcp_port *local_port, Ptr user_data, + TCPIOCompletionProc io_completion, Boolean async); + +#endif --- util.c Sun Jan 30 11:18:22 2022 +++ util.c Sun Jan 30 11:18:22 2022 @@ -0,0 +1,1099 @@ +/* + * Copyright (c) 2020-2022 joshua stein <jcs@jcs.org> + * + * Permission to use, copy, modify, and 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. + */ + +#include <stdarg.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#include "util.h" + +/* ALRT resources */ +#define ASK_ALERT_ID 130 + +#define ERROR_STRING_SIZE 1024 +static char err_str[ERROR_STRING_SIZE]; + +/* basic DITL with an ok button (1), text area (2), and icon (3) */ +#define ALERT_DITL_ICON 3 +static char alert_ditl[] = { + 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x46, + 0x00, 0xE6, 0x00, 0x5A, 0x01, 0x20, 0x04, 0x02, + 0x4F, 0x4B, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0A, + 0x00, 0x32, 0x00, 0x40, 0x01, 0x21, 0x08, 0x02, + 0x5E, 0x30, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0A, + 0x00, 0x0A, 0x00, 0x2A, 0x00, 0x2A, 0xA0, 0x02, + 0x00, 0x02 +}; +static Handle alert_ditl_h = NULL; + +/* DITL with a Yes button (1), No button (2), text (3), and icon (4) */ +static char ask_ditl[] = { + 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x46, + 0x00, 0xE6, 0x00, 0x5A, 0x01, 0x20, 0x04, 0x03, + 0x59, 0x65, 0x73, 0x21, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x46, 0x00, 0xA0, 0x00, 0x5A, 0x00, 0xDA, + 0x04, 0x02, 0x4E, 0x6F, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x0A, 0x00, 0x32, 0x00, 0x41, 0x01, 0x22, + 0x08, 0x02, 0x5E, 0x30, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x0A, 0x00, 0x0A, 0x00, 0x2A, 0x00, 0x2A, + 0xA0, 0x02, 0x00, 0x01 +}; +static Handle ask_ditl_h = NULL; + +/* DITL with just a text view */ +static char progress_ditl[] = { + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x14, + 0x00, 0x1E, 0x00, 0x32, 0x01, 0x3B, 0x08, 0x02, + 0x5E, 0x30 +}; +static Handle progress_ditl_h = NULL; +static DialogPtr progress_dialog = NULL; + +enum { + STOP_ALERT, + CAUTION_ALERT, + NOTE_ALERT +}; + +static TEHandle track_control_te = NULL; + +void vwarn(short alert_func, const char *format, va_list ap); + +/* + * Memory functions + */ + +void * +xmalloc(size_t size) +{ + void *ptr; + + if (size == 0) + panic("xmalloc: zero size"); + ptr = malloc(size); + if (ptr == NULL) + panic("xmalloc(%lu) failed", size); + return ptr; +} + +void * +xmalloczero(size_t size) +{ + void *ptr = xmalloc(size); + memset(ptr, 0, size); + return ptr; +} + +void * +xcalloc(size_t nmemb, size_t size) +{ + void *ptr; + + ptr = calloc(nmemb, size); + if (ptr == NULL) + panic("xcalloc(%lu, %lu) failed", nmemb, size); + return ptr; +} + +void * +xrealloc(void *src, size_t size) +{ + void *ret; + + ret = realloc(src, size); + if (ret == NULL) + panic("realloc(%lu) failed", size); + return ret; +} + +#define MUL_NO_OVERFLOW ((size_t)1 << (sizeof(size_t) * 4)) + +void * +xmallocarray(size_t nmemb, size_t size) +{ + if ((nmemb >= MUL_NO_OVERFLOW || size >= MUL_NO_OVERFLOW) && + nmemb > 0 && SIZE_MAX / nmemb < size) + panic("xmallocarray(%lu, %lu) failed", nmemb, size); + return xmalloc(size * nmemb); +} + +void * +xreallocarray(void *optr, size_t nmemb, size_t size) +{ + void *new_ptr; + + if ((nmemb >= MUL_NO_OVERFLOW || size >= MUL_NO_OVERFLOW) && + nmemb > 0 && SIZE_MAX / nmemb < size) + panic("xreallocarray(%lu, %lu) failed", nmemb, size); + return xrealloc(optr, size * nmemb); +} + +char * +xstrdup(const char *str) +{ + char *cp; + size_t len; + + len = strlen(str); + + cp = xmalloc(len + 1); + strlcpy(cp, str, len + 1); + + return cp; +} + +/* + * String functions + */ + +short +getline(char *str, size_t len, char **ret) +{ + short i; + + for (i = 0; i < len; i++) { + if (str[i] == '\r' || i == len - 1) { + if (*ret == NULL) + *ret = xmalloc(i + 1); + memcpy(*ret, str, i + 1); + (*ret)[i] = '\0'; + return i + 1; + } + } + + return 0; +} + +const char * +ordinal(unsigned short n) +{ + switch (n % 100) { + case 11: + case 12: + case 13: + return "th"; + default: + switch (n % 10) { + case 1: + return "st"; + case 2: + return "nd"; + case 3: + return "rd"; + default: + return "th"; + } + } +} + +long +strpos_quoted(char *str, char c) +{ + long pos; + unsigned char quot = 0; + + for (pos = 0; str[pos] != '\0'; pos++) { + if (quot) { + if (str[pos] == '\\') { + pos++; + continue; + } + if (str[pos] == quot) + quot = 0; + continue; + } else { + if (str[pos] == '"') { + quot = str[pos]; + continue; + } + if (str[pos] == c) + return pos; + } + } + + return -1; +} + +char * +OSTypeToString(OSType type) +{ + static char ostype_s[5]; + + ostype_s[0] = (unsigned char)((type >> 24) & 0xff); + ostype_s[1] = (unsigned char)((type >> 16) & 0xff); + ostype_s[2] = (unsigned char)((type >> 8) & 0xff); + ostype_s[3] = (unsigned char)(type & 0xff); + ostype_s[4] = 0; + + return ostype_s; +} + +/* + * BSD err(3) and warn(3) functions + */ + +void +vwarn(short alert_func, const char *format, va_list ap) +{ + Rect bounds, irect; + short quit = 0, height, width, hit; + WindowPtr win, dialog; + OSType itype; + Handle ihandle; + + GetPort(&win); + + vsnprintf(err_str, ERROR_STRING_SIZE, format, ap); + + width = 300; + height = 100; + bounds.left = (screenBits.bounds.right - width) / 2; + bounds.right = bounds.left + width; + bounds.top = GetMBarHeight() + + ((screenBits.bounds.bottom - height) / 2.5); + bounds.bottom = bounds.top + height; + + ParamText(CtoPstr(err_str), "\p", "\p", "\p"); + + alert_ditl_h = xNewHandle(sizeof(alert_ditl)); + HLock(alert_ditl_h); + memcpy(*alert_ditl_h, alert_ditl, sizeof(alert_ditl)); + HUnlock(alert_ditl_h); + + dialog = NewDialog(nil, &bounds, "\p", false, dBoxProc, + (WindowPtr)-1L, false, 0, alert_ditl_h); + +#if 0 + /* XXX: why doesn't changing this work? */ + GetDItem(dialog, ALERT_DITL_ICON, &itype, &ihandle, &irect); + switch (alert_func) { + case CAUTION_ALERT: + ihandle = GetIcon(cautionIcon); + break; + case NOTE_ALERT: + ihandle = GetIcon(noteIcon); + break; + default: + ihandle = GetIcon(stopIcon); + } + ihandle = GetIcon(cautionIcon); + SetDItem(dialog, ALERT_DITL_ICON, itype, ihandle, &irect); +#endif + + ShowWindow(dialog); + for (;;) { + ModalDialog(0, &hit); + if (hit == ok) + break; + } + DisposDialog(dialog); + DisposHandle(alert_ditl_h); + + SetPort(win); + + if (quit) + ExitToShell(); +} + +void +panic(const char *format, ...) +{ + va_list ap; + + va_start(ap, format); + vwarn(STOP_ALERT, format, ap); + va_end(ap); + + ExitToShell(); +} + +void +err(short ret, const char *format, ...) +{ + va_list ap; + + va_start(ap, format); + vwarn(STOP_ALERT, format, ap); + va_end(ap); + + ExitToShell(); +} + +void +warn(const char *format, ...) +{ + va_list ap; + + va_start(ap, format); + vwarn(CAUTION_ALERT, format, ap); + va_end(ap); +} + +void +warnx(const char *format, ...) +{ + va_list ap; + + va_start(ap, format); + vwarn(CAUTION_ALERT, format, ap); + va_end(ap); +} + +void +note(const char *format, ...) +{ + va_list ap; + + va_start(ap, format); + vwarn(NOTE_ALERT, format, ap); + va_end(ap); +} + +short +ask(const char *format, ...) +{ + Rect bounds, irect; + short height, width, hit; + WindowPtr win, dialog; + OSType itype; + Handle ihandle; + va_list ap; + + GetPort(&win); + + va_start(ap, format); + vsnprintf(err_str, ERROR_STRING_SIZE, format, ap); + va_end(ap); + + width = 300; + height = 100; + bounds.left = (screenBits.bounds.right - width) / 2; + bounds.right = bounds.left + width; + bounds.top = GetMBarHeight() + + ((screenBits.bounds.bottom - height) / 2.5); + bounds.bottom = bounds.top + height; + + ParamText(CtoPstr(err_str), "\p", "\p", "\p"); + + ask_ditl_h = xNewHandle(sizeof(ask_ditl)); + HLock(ask_ditl_h); + memcpy(*ask_ditl_h, ask_ditl, sizeof(ask_ditl)); + HUnlock(ask_ditl_h); + + dialog = NewDialog(nil, &bounds, "\p", false, dBoxProc, + (WindowPtr)-1L, false, 0, ask_ditl_h); + + ShowWindow(dialog); + for (;;) { + ModalDialog(0, &hit); + if (hit == 1 || hit == 2) + break; + } + DisposDialog(dialog); + DisposHandle(alert_ditl_h); + + SetPort(win); + + return (hit == 1); +} + +void +progress(char *format, ...) +{ + static Str255 progress_s; + Handle thandle; + va_list argptr; + Rect bounds = { 100, 90, 160, 420 }; /* tlbr */ + Rect trect; + static WindowPtr progress_win; + short ttype; + + if (format == NULL) { + if (progress_dialog != NULL) { + DisposDialog(progress_dialog); + DisposHandle(progress_ditl_h); + progress_dialog = NULL; + } + SetPort(progress_win); + return; + } + + va_start(argptr, format); + vsnprintf((char *)progress_s, 256, format, argptr); + va_end(argptr); + CtoPstr(progress_s); + + if (progress_dialog == NULL) { + GetPort(&progress_win); + + progress_ditl_h = xNewHandle(sizeof(progress_ditl)); + HLock(progress_ditl_h); + memcpy(*progress_ditl_h, progress_ditl, sizeof(progress_ditl)); + HUnlock(progress_ditl_h); + + progress_dialog = NewDialog(nil, &bounds, "\p", false, dBoxProc, + (WindowPtr)-1L, false, 0, progress_ditl_h); + } + + GetDItem(progress_dialog, 1, &ttype, &thandle, &trect); + SetIText(thandle, progress_s); + + ShowWindow(progress_dialog); + DrawDialog(progress_dialog); +} + +/* + * General Mac-specific non-GUI functions + */ +static unsigned long _xorshift_state = 0; +unsigned long +xorshift32(void) +{ + unsigned long x = _xorshift_state; + if (x == 0) + x = Ticks; + x ^= x << 13; + x ^= x >> 17; + x ^= x << 5; + return _xorshift_state = x; +} + + +/* + * Error checking wrappers for Mac toolkit functions + */ + +Handle +xNewHandle(size_t size) +{ + Handle h; + + if (size == 0) + panic("Zero xNewHandle size"); + + h = NewHandle(size); + if (h == NULL) + panic("Failed to NewHandle(%lu)", size); + + return h; +} + +Handle +xGetResource(ResType type, short id) +{ + Handle h; + + h = GetResource(type, id); + if (h == NULL) + panic("Failed to find resource %d", id); + + return h; +} + +StringHandle +xGetString(short id) +{ + StringHandle h; + + h = GetString(id); + if (h == NULL) + panic("Failed to find STR resource %d", id); + + return h; +} + +char * +xGetStringAsChar(short id) +{ + StringHandle h = xGetString(id); + char *out; + size_t l; + + HLock(h); + + l = (*h)[0]; + out = xmalloc(l + 1); + memcpy((void *)out, (void *)(*h + 1), l); + out[l] = '\0'; + + DisposHandle(h); + return out; +} + +long +xGetStringAsLong(short id) +{ + char *c; + long r; + + c = xGetStringAsChar(id); + r = atol(c); + free(c); + return r; +} + +void +xSetHandleSize(Handle h, Size s) +{ + SetHandleSize(h, s); + if (MemError()) + panic("Failed to SetHandleSize to %ld", s); +} + +/* + * Filesystem utilities + */ + +short +getpath(short vRefNum, Str255 fileName, Str255 *ret, bool include_file) +{ + WDPBRec wdir; + HVolumeParam wvol; + DirInfo wcinfo; + Str255 name; + size_t retlen = 0, len; + char tmpret[256], tmp[256]; + char *lastcolon; + + if (strchr((char *)fileName + 1, ':') != NULL) { + /* already a full path */ + memcpy(*ret, fileName, 256); + if (!include_file) { + PtoCstr(*ret); + lastcolon = strrchr((char *)*ret, ':'); + lastcolon[0] = '\0'; + CtoPstr(*ret); + } + return 0; + } + + wdir.ioVRefNum = wdir.ioWDVRefNum = vRefNum; + wdir.ioWDIndex = 0; + wdir.ioWDProcID = 0; + wdir.ioNamePtr = (StringPtr)&name; + if (PBGetWDInfo(&wdir, 0) != noErr) { + warn("Failed looking up directory"); + return 1; + } + + wvol.ioNamePtr = (StringPtr)&name; + wvol.ioVRefNum = vRefNum; + wvol.ioVolIndex = 0; + + if (PBHGetVInfoSync((HParmBlkPtr)&wvol) != noErr) { + warn("Failed getting volume info"); + return 1; + } + + if (wvol.ioVSigWord != 0x4244) { + warn("Unknown filesystem type 0x%x", wvol.ioVSigWord); + return 1; + } + + wcinfo.ioVRefNum = vRefNum; + wcinfo.ioNamePtr = (StringPtr)&name; + wcinfo.ioFDirIndex = -1; + wcinfo.ioDrParID = wdir.ioWDDirID; + wcinfo.ioDrDirID = wdir.ioWDDirID; + + /* go backwards, prepending each folder's parent */ + while (wcinfo.ioDrParID != 1) { + wcinfo.ioDrDirID = wcinfo.ioDrParID; /* .. */ + + if (PBGetCatInfo((CInfoPBPtr)&wcinfo, 0) != noErr) + break; + + len = name[0]; + PtoCstr(name); + if (retlen == 0) { + retlen = len; + strlcpy(tmpret, (char *)name, sizeof(tmpret)); + } else { + strlcpy(tmp, tmpret, sizeof(tmp)); + snprintf(tmpret, sizeof(tmpret), "%s:%s", name, tmp); + } + } + + if (include_file) { + /* append the original path */ + memcpy(name, fileName, sizeof(name)); + PtoCstr(name); + if (retlen == 0) + strlcpy(tmpret, (char *)name, sizeof(tmpret)); + else { + strlcat(tmpret, ":", sizeof(tmpret)); + strlcat(tmpret, (char *)name, sizeof(tmpret)); + } + } else if (retlen == 0) { + (*ret)[0] = 0; + return 0; + } + + CtoPstr(tmpret); + memcpy(*ret, tmpret, sizeof(tmpret)); + + return 0; +} + +short +stat(char *path, struct stat *sb) +{ + char *ppath; + short ret; + + ppath = xstrdup(path); + CtoPstr(ppath); + ret = FStat((unsigned char *)ppath, sb); + free(ppath); + + return ret; +} + +short +FStat(Str255 path, struct stat *sb) +{ + CInfoPBRec catblock = { 0 }; + short ret; + + catblock.hFileInfo.ioNamePtr = path; + ret = PBGetCatInfo(&catblock, 0); + if (ret != noErr) + return -1; + + /* data fork logical length + resource fork logical length */ + sb->st_size = catblock.hFileInfo.ioFlLgLen + + catblock.hFileInfo.ioFlRLgLen; + sb->st_ctime = catblock.hFileInfo.ioFlCrDat; + sb->st_mtime = catblock.hFileInfo.ioFlMdDat; + sb->st_flags = catblock.hFileInfo.ioFlAttrib; + + return 0; +} + +bool +FIsDir(Str255 path) +{ + struct stat st; + short ret; + + if ((ret = FStat(path, &st)) != 0) + return ret; + + /* bit 4 is set in ioFlAttrib if the item is a directory */ + if (st.st_flags & (1 << 4)) + return true; + + return false; +} + +OSErr +copy_file(Str255 source, Str255 dest, bool overwrite) +{ + FInfo fi; + IOParam pb; + short error, source_ref, dest_ref; + + /* copy data fork */ + + error = GetFInfo(source, 0, &fi); + if (error) + return error; + + error = Create(dest, 0, fi.fdCreator, fi.fdType); + if (error == dupFNErr && overwrite) { + error = FSDelete(dest, 0); + if (error) + return error; + error = Create(dest, 0, fi.fdCreator, fi.fdType); + } + if (error) + return error; + + memset(&pb, 0, sizeof(pb)); + pb.ioNamePtr = source; + pb.ioPermssn = fsRdPerm; + error = PBOpen(&pb, false); + if (error) + return error; + source_ref = pb.ioRefNum; + + error = FSOpen(dest, 0, &dest_ref); + if (error) { + FSClose(source_ref); + return error; + } + + error = copy_file_contents(source_ref, dest_ref); + + FSClose(source_ref); + FSClose(dest_ref); + + if (error) + return error; + + /* + * Copy resource fork, open source as shared read/write in case it's + * an open resource file. + */ + source_ref = OpenRFPerm(source, 0, fsRdWrShPerm); + + if (source_ref == -1 && ResError() == eofErr) { + /* no resource fork */ + FSClose(source_ref); + FSClose(dest_ref); + return 0; + } + + if (source_ref == -1) + return ResError(); + + CreateResFile(dest); + if (ResError()) { + FSClose(source_ref); + return ResError(); + } + error = OpenRF(dest, 0, &dest_ref); + if (error) { + FSClose(source_ref); + return error; + } + + error = copy_file_contents(source_ref, dest_ref); + + FSClose(source_ref); + FSClose(dest_ref); + + return error; +} + +OSErr +copy_file_contents(short source_ref, short dest_ref) +{ + char buf[1024]; + short error; + long source_size, count; + + GetEOF(source_ref, &source_size); + error = SetFPos(source_ref, fsFromStart, 0); + if (error) + return error; + error = SetEOF(dest_ref, source_size); + if (error) + return error; + error = SetFPos(dest_ref, fsFromStart, 0); + if (error) + return error; + while (source_size > 0) { + count = sizeof(buf); + if (count > source_size) + count = source_size; + error = FSRead(source_ref, &count, &buf); + if (error && error != eofErr) + break; + source_size -= count; + error = FSWrite(dest_ref, &count, &buf); + if (error && error != eofErr) + break; + } + + if (error && error != eofErr) + return error; + + return 0; +} + +/* read a \r-terminated line or the final non-line bytes of an open file */ +OSErr +FSReadLine(short frefnum, char *buf, size_t buflen) +{ + char tbuf; + size_t pos, fsize, rlen = 1, total_read = 0; + short error, found = -1, i; + + GetFPos(frefnum, &pos); + GetEOF(frefnum, &fsize); + + for (; pos <= fsize; pos++) { + if (total_read > buflen) + return -1; + + error = FSRead(frefnum, &rlen, &tbuf); + if (error) + return -1; + + if (tbuf == '\r') + return total_read; + + buf[total_read++] = tbuf; + } + + /* nothing found until the end of the file */ + return total_read; +} + + +/* + * General Mac-specific GUI functions + */ + +short +FontHeight(short font_id, short size) +{ + FMInput f_in; + FMOutput *f_out; + + if (font_id == -1) + font_id = thePort->txFont; + if (size == -1) + size = thePort->txSize; + + f_in.family = font_id; + f_in.size = size; + f_in.face = 0; + f_in.needBits = false; + f_in.device = 0; + f_in.numer.h = f_in.numer.v = 1; + f_in.denom.h = f_in.denom.v = 1; + + f_out = FMSwapFont(&f_in); + + return (((f_out->leading + f_out->ascent + f_out->descent) * + f_out->numer.v) / f_out->denom.v); +} + +void +DrawGrowIconOnly(WindowPtr win) +{ + Rect r; + RgnHandle tmp; + + GetClip(tmp = NewRgn()); + r = win->portRect; + r.top = r.bottom - SCROLLBAR_WIDTH; + r.left = r.right - SCROLLBAR_WIDTH + 1; + ClipRect(&r); + DrawGrowIcon(win); + SetClip(tmp); + DisposeRgn(tmp); +} + +/* assumes a fixed-width style */ +short +TEGetWidth(short off, TEHandle te) +{ + TextStyle style; + short fheight, fwidth, ascent, old_font, old_size; + + TEGetStyle(off, &style, &fheight, &ascent, te); + + old_font = thePort->txFace; + old_size = thePort->txSize; + thePort->txFace = style.tsFont; + thePort->txSize = style.tsSize; + fwidth = CharWidth('m'); + thePort->txFace = old_font; + thePort->txSize = old_size; + + return fwidth; +} + +#define ceildiv(a,b) ((a / b) + (!!(a % b))) + +void +UpdateScrollbarForTE(ControlHandle control, TEHandle te, bool reset) +{ + size_t vlines, telines; + TERec *ter; + short fheight, fwidth, max, val, per_page, per_line, horiz, max_chars, + n; + + HLock(te); + ter = *te; + + horiz = (((*control)->contrlRect.bottom - (*control)->contrlRect.top) < + ((*control)->contrlRect.right - (*control)->contrlRect.left)); + + if (horiz) { + fwidth = TEGetWidth(0, te); + per_line = ((ter->viewRect.right - ter->viewRect.left) / + fwidth) - 1; + for (max_chars = 0, n = 1; n < ter->nLines; n++) { + if (ter->lineStarts[n] - ter->lineStarts[n - 1] > max_chars) + max_chars = ter->lineStarts[n] - ter->lineStarts[n - 1]; + } + + if (max_chars <= per_line) + /* don't enable the scrollbar */ + max = 1; + else + max = max_chars - per_line + 1; + + if (reset) { + val = 1; + TESetSelect(0, 0, te); + } else { + val = (ter->viewRect.left - ter->destRect.left); + + if (val < 0) + val = 1; + else { + val = ceildiv(val, fwidth) + 1; + if (val > max) + max = val; + } + } + } else { + fheight = TEGetHeight(0, 0, te); + vlines = (ter->viewRect.bottom - ter->viewRect.top) / fheight; + telines = ter->nLines; + /* telines is inaccurate if the last line doesn't have any chars */ + if (telines >= vlines) + telines++; + max = telines - vlines + 1; + if (max < 1) + max = 1; + + if (reset) { + val = 1; + TESetSelect(0, 0, te); + } else { + val = (ter->viewRect.top - ter->destRect.top); + if (val < 0) + val = 1; + else { + val = ceildiv(val, fheight) + 1; + if (val > max) + max = val; + } + } + } + SetCtlMax(control, max); + SetCtlValue(control, val); + + HUnlock(te); +} + +void +SetTrackControlTE(TEHandle te) +{ + track_control_te = te; +} + +pascal void +TrackMouseDownInControl(ControlHandle control, short part) +{ + TERec *ter; + short page, val, adj, fheight, fwidth, horiz, per_line; + + if (track_control_te == NULL) + panic("TrackMouseDownInControl without SetTrackControlTE"); + + HLock(track_control_te); + ter = *track_control_te; + + horiz = (((*control)->contrlRect.bottom - (*control)->contrlRect.top) < + ((*control)->contrlRect.right - (*control)->contrlRect.left)); + + if (horiz) { + fwidth = TEGetWidth(0, track_control_te); + per_line = ((ter->viewRect.right - ter->viewRect.left) / + fwidth) - 1; + page = ceildiv(GetCtlMax(control) + per_line, + ((per_line * 75) / 100)) + 1; + } else { + /* keep 1 line of context between pages */ + fheight = TEGetHeight(0, 0, track_control_te); + page = ((ter->viewRect.bottom - ter->viewRect.top) / fheight) - 1; + } + + adj = 0; + switch (part) { + case inUpButton: + adj = -1; + break; + case inPageUp: + adj = -page; + break; + case inDownButton: + adj = 1; + break; + case inPageDown: + adj = page; + break; + } + + val = GetCtlValue(control); + if (val + adj < GetCtlMin(control)) + adj = -(val - GetCtlMin(control)); + if (val + adj > GetCtlMax(control)) + adj = (GetCtlMax(control) - val); + if (adj == 0) + return; + + if (horiz) + TEScroll(-adj * fwidth, 0, track_control_te); + else + TEScroll(0, -adj * fheight, track_control_te); + + SetCtlValue(control, val + adj); + + HUnlock(track_control_te); +} + +pascal bool +ModalDialogFilter(DialogPtr dlg, EventRecord *event, short *hit) +{ + char key; + + switch (event->what) { + case inGoAway: + if (TrackGoAway(dlg, event->where)) { + *hit = -1; + return true; + } + break; + case keyDown: + key = event->message & charCodeMask; + + if (event->modifiers & cmdKey) { + switch (key) { + case 'x': + ZeroScrap(); + DlgCut(dlg); + break; + case 'c': + ZeroScrap(); + DlgCopy(dlg); + break; + case 'v': + if (TEFromScrap() == noErr) + DlgPaste(dlg); + break; + } + event->what = nullEvent; + return false; + } else if (key == 13 || key == 3) { + /* OK button */ + *hit = OK; + return true; + } + + break; + } + + return false; +} --- util.h Sat Jan 29 16:52:36 2022 +++ util.h Sat Jan 29 16:52:36 2022 @@ -0,0 +1,125 @@ +/* + * Copyright (c) 2020-2022 joshua stein <jcs@jcs.org> + * + * Permission to use, copy, modify, and 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. + */ + +#ifndef __UTIL_H__ +#define __UTIL_H__ + +#include <stdlib.h> +#include <limits.h> +#include <time.h> + +#ifndef SIZE_MAX +#define SIZE_MAX ULONG_MAX +#endif + +#define nitems(what) (sizeof((what)) / sizeof((what)[0])) +#define member_size(type, member) sizeof(((type *)0)->member) + +#define MIN(a, b) ((a) < (b) ? (a) : (b)) +#define MAX(a, b) ((a) > (b) ? (a) : (b)) +#define BOUND(a, min, max) ((a) > (max) ? (max) : ((a) < (min) ? (min) : (a))) + +#define EXPAND_TO_FIT(var, var_size, used_size, add, grow_amount) { \ + if ((used_size) + (add) >= (var_size)) { \ + while ((used_size) + (add) >= (var_size)) { \ + (var_size) += (grow_amount); \ + } \ + (var) = xrealloc((var), (var_size)); \ + } \ +} + +#define SCROLLBAR_WIDTH 16 + +/* GetMBarHeight() is not very useful */ +#define MENUBAR_HEIGHT 20 + +#define MAX_TEXTEDIT_SIZE 32767L + +#ifndef bool +typedef Boolean bool; +#endif + +typedef signed long off_t; +typedef signed long ssize_t; +typedef unsigned char u_char; +typedef unsigned long u_int; + +typedef struct { + short push[2], rts; + void *addr; +} tCodeStub; + +typedef struct stat { + short st_mode; + ssize_t st_size; + time_t st_ctime; + time_t st_mtime; + unsigned char st_flags; +}; + +void *xmalloc(size_t); +void *xmalloczero(size_t); +void *xcalloc(size_t, size_t); +void *xrealloc(void *src, size_t size); +void *xmallocarray(size_t nmemb, size_t size); +void *xreallocarray(void *, size_t, size_t); +char *xstrdup(const char *); + +short getline(char *str, size_t len, char **ret); +size_t strlcpy(char *dst, const char *src, size_t dsize); +size_t strlcat(char *dst, const char *src, size_t dsize); +const char *ordinal(unsigned short n); +long strpos_quoted(char *str, char c); +char *OSTypeToString(OSType type); + +unsigned long xorshift32(void); + +void panic(const char *format, ...); +void err(short ret, const char *format, ...); +void warnx(const char *format, ...); +void warn(const char *format, ...); +void note(const char *format, ...); +short ask(const char *format, ...); +#define ASK_YES 1 +#define ASK_NO 2 +void progress(char *format, ...); + +Handle xNewHandle(size_t size); +Handle xGetResource(ResType type, short id); +StringHandle xGetString(short id); +char *xGetStringAsChar(short id); +long xGetStringAsLong(short id); +void xSetHandleSize(Handle h, Size s); + +short getpath(short vRefNum, Str255 fileName, Str255 *ret, + bool include_file); +bool FIsDir(Str255 path); +short stat(char *path, struct stat *sb); +short FStat(Str255 path, struct stat *sb); +OSErr copy_file(Str255 source, Str255 dest, bool overwrite); +OSErr copy_file_contents(short source_ref, short dest_ref); +OSErr FSReadLine(short frefnum, char *buf, size_t buflen); + +short FontHeight(short font_id, short size); +void DrawGrowIconOnly(WindowPtr win); +short TEGetWidth(short off, TEHandle te); +void UpdateScrollbarForTE(ControlHandle scroller, TEHandle te, bool reset); +void SetTrackControlTE(TEHandle te); +pascal void TrackMouseDownInControl(ControlHandle control, short part); +pascal bool ModalDialogFilter(DialogPtr dlg, EventRecord *event, + short *hit); + +#endif