jcs
/wikipedia
/amendments
/1
*: Import skeleton code
jcs made amendment 1 over 2 years ago
--- browser.c Thu Aug 18 22:03:09 2022
+++ browser.c Thu Aug 18 22:03:09 2022
@@ -0,0 +1,298 @@
+/*
+ * 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 <stdarg.h>
+#include <stdio.h>
+#include <string.h>
+
+#include "wikipedia.h"
+#include "browser.h"
+#include "focusable.h"
+#include "util.h"
+
+#define PADDING 10
+
+bool browser_close(struct focusable *focusable);
+void browser_idle(struct focusable *focusable, EventRecord *event);
+void browser_update_menu(struct browser *browser);
+void browser_update(struct focusable *focusable, EventRecord *event);
+void browser_key_down(struct focusable *focusable, EventRecord *event);
+void browser_mouse_down(struct focusable *focusable, EventRecord *event);
+bool browser_handle_menu(struct focusable *focusable, short menu,
+ short item);
+
+Pattern fill_pattern;
+
+void
+browser_idle(struct focusable *focusable, EventRecord *event)
+{
+ struct browser *browser = (struct browser *)focusable->cookie;
+#if 0
+ switch (browser->state) {
+ case BROWSER_STATE_IDLE:
+ if (browser->need_refresh) {
+ browser->need_refresh = false;
+ browser->state = BROWSER_STATE_UPDATE_AMENDMENT_LIST;
+ }
+ break;
+ case BROWSER_STATE_ADD_FILE:
+ if (repo_add_file(browser->repo) == NULL)
+ browser->state = BROWSER_STATE_IDLE;
+ else
+ browser->state = BROWSER_STATE_UPDATE_FILE_LIST;
+ break;
+ case BROWSER_STATE_UPDATE_FILE_LIST:
+ browser_add_files(browser);
+ browser->state = BROWSER_STATE_IDLE;
+ break;
+ case BROWSER_STATE_UPDATE_AMENDMENT_LIST:
+ browser_filter_amendments(browser);
+ browser->state = BROWSER_STATE_IDLE;
+ break;
+ case BROWSER_STATE_OPEN_COMMITTER:
+ committer_init(browser);
+ browser->state = BROWSER_STATE_WAITING_FOR_COMMITTER;
+ break;
+ case BROWSER_STATE_WAITING_FOR_COMMITTER:
+ break;
+ case BROWSER_STATE_DISCARD_CHANGES:
+ browser_discard_changes(browser);
+ browser->state = BROWSER_STATE_IDLE;
+ break;
+ case BROWSER_STATE_EXPORT_PATCH:
+ browser_export_patch(browser);
+ browser->state = BROWSER_STATE_IDLE;
+ break;
+ case BROWSER_STATE_APPLY_PATCH:
+ browser_apply_patch(browser);
+ browser->state = BROWSER_STATE_IDLE;
+ break;
+ case BROWSER_STATE_EDIT_AMENDMENT:
+ browser_edit_amendment(browser);
+ browser->state = BROWSER_STATE_IDLE;
+ break;
+ }
+#endif
+}
+
+struct browser *
+browser_init(void)
+{
+ char title[256];
+ struct browser *browser;
+ struct focusable *focusable;
+ Rect bounds = { 0 }, te_bounds = { 0 };
+ Rect data_bounds = { 0, 0, 0, 1 }; /* tlbr */
+ Point cell_size = { 0, 0 };
+ Cell cell = { 0 };
+ short n;
+
+ browser = xmalloczero(sizeof(struct browser), "browser");
+ browser->state = BROWSER_STATE_IDLE;
+
+ GetIndPattern(&fill_pattern, sysPatListID, 22);
+
+ /* main window */
+ bounds.left = (PADDING / 2);
+ bounds.top = screenBits.bounds.top + (GetMBarHeight() * 2) - 1 +
+ (PADDING / 2);
+ bounds.right = screenBits.bounds.right - 1 - (PADDING / 2);
+ bounds.bottom = screenBits.bounds.bottom - 1 - (PADDING / 2);
+
+ snprintf(title, sizeof(title), "%s", PROGRAM_NAME);
+ CtoPstr(title);
+ browser->win = NewWindow(0L, &bounds, title, false, noGrowDocProc,
+ (WindowPtr)-1L, true, 0);
+ if (!browser->win)
+ err(1, "Can't create window");
+ SetPort(browser->win);
+
+ bounds.top = PADDING;
+ bounds.left = PADDING;
+ bounds.right = browser->win->portRect.right - SCROLLBAR_WIDTH - PADDING;
+ bounds.bottom = browser->win->portRect.bottom - PADDING;
+ te_bounds = bounds;
+ InsetRect(&te_bounds, 2, 2);
+ browser->te = TEStylNew(&te_bounds, &bounds);
+ TEAutoView(true, browser->te);
+ (*(browser->te))->caretHook = NullCaretHook;
+ TEActivate(browser->te);
+
+ /* scrollbar for diff text */
+ bounds.right = browser->win->portRect.right - PADDING;
+ bounds.left = bounds.right - SCROLLBAR_WIDTH;
+ bounds.bottom++;
+ bounds.top--;
+ browser->te_scroller = NewControl(browser->win, &bounds, "\p", true,
+ 1, 1, 1, scrollBarProc, 0L);
+
+ browser_update_menu(browser);
+ UpdateScrollbarForTE(browser->te_scroller, browser->te, true);
+
+ focusable = xmalloczero(sizeof(struct focusable), "focusable");
+ focusable->cookie = browser;
+ focusable->win = browser->win;
+ focusable->idle = browser_idle;
+ focusable->update = browser_update;
+ focusable->mouse_down = browser_mouse_down;
+ focusable->menu = browser_handle_menu;
+ focusable->close = browser_close;
+ focusable_add(focusable);
+
+ return browser;
+}
+
+bool
+browser_close(struct focusable *focusable)
+{
+ struct browser *browser = (struct browser *)focusable->cookie;
+
+ TEDispose(browser->te);
+ DisposeWindow(browser->win);
+
+ free(browser);
+
+ return true;
+}
+
+void
+browser_update_menu(struct browser *browser)
+{
+ size_t vlines;
+ TERec *te;
+ Cell cell = { 0, 0 };
+
+ TextFont(systemFont);
+ TextSize(12);
+
+ te = *(browser->te);
+
+ DisableItem(edit_menu, EDIT_MENU_CUT_ID);
+
+ if (te->selStart == te->selEnd)
+ DisableItem(edit_menu, EDIT_MENU_COPY_ID);
+ else
+ EnableItem(edit_menu, EDIT_MENU_COPY_ID);
+
+ DisableItem(edit_menu, EDIT_MENU_PASTE_ID);
+
+ if (te->nLines == 0)
+ DisableItem(edit_menu, EDIT_MENU_SELECT_ALL_ID);
+ else
+ EnableItem(edit_menu, EDIT_MENU_SELECT_ALL_ID);
+}
+
+void
+browser_update(struct focusable *focusable, EventRecord *event)
+{
+ struct browser *browser = (struct browser *)focusable->cookie;
+ Str255 buf;
+ Rect r;
+ short what = -1;
+
+ if (event != NULL)
+ what = event->what;
+
+ switch (what) {
+ case -1:
+ case updateEvt:
+ FillRect(&browser->win->portRect, fill_pattern);
+
+ r = (*(browser->te))->viewRect;
+ FillRect(&r, white);
+ TEUpdate(&r, browser->te);
+ InsetRect(&r, -1, -1);
+ FrameRect(&r);
+
+ UpdtControl(browser->win, browser->win->visRgn);
+
+ browser_update_menu(browser);
+
+ break;
+ }
+}
+
+void
+browser_mouse_down(struct focusable *focusable, EventRecord *event)
+{
+ Cell selected = { 0 }, now = { 0 };
+ Point p;
+ ControlHandle control;
+ Rect r;
+ struct browser *browser = (struct browser *)focusable->cookie;
+ short val, adj, page, was_selected, part, i, data_len;
+ char *path;
+
+ p = event->where;
+ GlobalToLocal(&p);
+
+ /* is it in TE? */
+ r = (*(browser->te))->viewRect;
+ if (PtInRect(p, &r)) {
+ TEClick(p, ((event->modifiers & shiftKey) != 0), browser->te);
+ browser_update_menu(browser);
+ return;
+ }
+
+ switch (part = FindControl(p, browser->win, &control)) {
+ case inButton:
+ break;
+ case inUpButton:
+ case inDownButton:
+ case inPageUp:
+ case inPageDown:
+ if (control == browser->te_scroller)
+ SetTrackControlTE(browser->te);
+ else
+ break;
+ 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 == browser->te_scroller)
+ TEScroll(0, adj * TEGetHeight(0, 0, browser->te),
+ browser->te);
+ SetCtlValue(control, val);
+ }
+ break;
+ }
+}
+
+bool
+browser_handle_menu(struct focusable *focusable, short menu, short item)
+{
+ struct browser *browser = (struct browser *)focusable->cookie;
+
+ switch (menu) {
+ case EDIT_MENU_ID:
+ switch (item) {
+ case EDIT_MENU_COPY_ID:
+ TECopy(browser->te);
+ return true;
+ case EDIT_MENU_SELECT_ALL_ID:
+ TESetSelect(0, 1024 * 32, browser->te);
+ return true;
+ }
+ break;
+ }
+
+ return false;
+}
--- browser.h Thu Aug 18 22:01:49 2022
+++ browser.h Thu Aug 18 22:01:49 2022
@@ -0,0 +1,35 @@
+/*
+ * 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.
+ */
+
+#ifndef __BROWSER_H__
+#define __BROWSER_H__
+
+#include <stdlib.h>
+
+enum {
+ BROWSER_STATE_IDLE
+};
+
+struct browser {
+ short state;
+ WindowPtr win;
+ TEHandle te;
+ ControlHandle te_scroller;
+};
+
+struct browser *browser_init(void);
+
+#endif
--- dnr.c Thu Aug 18 21:15:47 2022
+++ dnr.c Thu Aug 18 21:15:47 2022
@@ -0,0 +1,347 @@
+/*
+ * 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 "dnr.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);
+pascal void StrToAddrMarkDone(struct hostInfo *hi, char *data);
+
+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
+
+pascal void
+StrToAddrMarkDone(struct hostInfo *hi, char *data)
+{
+ volatile int *done = (int *)data;
+ *done = 1;
+}
+
+OSErr
+ResolveName(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;
+}
--- dnr.h Thu Aug 18 21:13:21 2022
+++ dnr.h Thu Aug 18 21:13:21 2022
@@ -0,0 +1,76 @@
+/*
+ dnr.h (formerly 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
+
+struct hostInfo {
+ short _pad; /* XXX: i don't know why this is needed, but without it,
+ * StrToAddrProcPtr() returns everything shifted 2 bytes */
+ short rtnCode;
+ char cname[255];
+ unsigned long addr[NUM_ALT_ADDRS];
+};
+
+enum AddrClasses {
+ A = 1,
+ NS,
+ CNAME = 5,
+ lastClass = 65535
+};
+
+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);
+
+OSErr ResolveName(char **name, unsigned long *ipAddress);
+
+#endif
--- focusable.c Thu Aug 18 21:14:38 2022
+++ focusable.c Thu Aug 18 21:14:38 2022
@@ -0,0 +1,177 @@
+/*
+ * 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 "focusable.h"
+#include "util.h"
+
+struct focusable **focusables = NULL;
+short nfocusables = 0;
+
+struct focusable *
+focusable_find(GrafPtr win)
+{
+ short n;
+
+ for (n = 0; n < nfocusables; n++) {
+ if (focusables[n]->win == win)
+ return focusables[n];
+ }
+
+ return NULL;
+}
+
+struct focusable *
+focusable_focused(void)
+{
+ if (nfocusables && focusables[0]->visible)
+ return focusables[0];
+
+ return NULL;
+}
+
+void
+focusable_add(struct focusable *focusable)
+{
+ short n;
+
+ nfocusables++;
+ focusables = xreallocarray(focusables, sizeof(struct focusable *),
+ nfocusables);
+
+ if (nfocusables > 1)
+ for (n = nfocusables - 1; n > 0; n--)
+ focusables[n] = focusables[n - 1];
+
+ focusables[0] = focusable;
+
+ focusable_show(focusable);
+}
+
+bool
+focusable_show(struct focusable *focusable)
+{
+ struct focusable *last, *tmp;
+ short n;
+
+ if (nfocusables > 1 && focusables[0] != focusable) {
+ last = focusables[0];
+
+ if (last->modal)
+ /* other focusables cannot steal focus from modals */
+ return false;
+
+ focusables[0] = focusable;
+ for (n = 1; n < nfocusables; n++) {
+ tmp = focusables[n];
+ focusables[n] = last;
+ last = tmp;
+ if (last == focusable)
+ break;
+ }
+ }
+
+ if (!focusable->visible) {
+ focusable->visible = true;
+ ShowWindow(focusable->win);
+ }
+
+ SelectWindow(focusable->win);
+ SetPort(focusable->win);
+
+ return true;
+}
+
+bool
+focusable_close(struct focusable *focusable)
+{
+ short n;
+
+ if (focusable->close) {
+ if (!focusable->close(focusable))
+ return false;
+ } else
+ DisposeWindow(focusable->win);
+
+ for (n = 0; n < nfocusables; n++) {
+ if (focusables[n] == focusable) {
+ for (; n < nfocusables - 1; n++)
+ focusables[n] = focusables[n + 1];
+ break;
+ }
+ }
+
+ nfocusables--;
+ if (nfocusables)
+ focusables = xreallocarray(focusables, sizeof(Ptr), nfocusables);
+ else {
+ free(focusables);
+ focusables = NULL;
+ }
+
+ if (nfocusables && focusables[0]->visible)
+ focusable_show(focusables[0]);
+
+ return true;
+}
+
+void
+focusable_hide(struct focusable *focusable)
+{
+ short n;
+
+ HideWindow(focusable->win);
+ focusable->visible = false;
+
+ for (n = 0; n < nfocusables; n++) {
+ if (focusables[n] == focusable) {
+ for (; n < nfocusables - 1; n++)
+ focusables[n] = focusables[n + 1];
+ break;
+ }
+ }
+ focusables[nfocusables - 1] = focusable;
+}
+
+bool
+focusables_quit(void)
+{
+ short tnfocusables = nfocusables;
+ short n;
+ struct focusable **tfocusables;
+ bool quit = true;
+
+ if (nfocusables) {
+ /*
+ * nfocusables and focusables array will probably be
+ * modified as each focusable quits
+ */
+ tfocusables = xcalloc(sizeof(Ptr), tnfocusables, "tfocusables");
+ memcpy(tfocusables, focusables, sizeof(Ptr) * tnfocusables);
+
+ for (n = 0; n < tnfocusables; n++) {
+ if (tfocusables[n] && !focusable_close(tfocusables[n])) {
+ quit = false;
+ break;
+ }
+ }
+
+ free(tfocusables);
+ }
+
+ return quit;
+}
--- focusable.h Tue Aug 16 13:33:08 2022
+++ focusable.h Tue Aug 16 13:33:08 2022
@@ -0,0 +1,49 @@
+/*
+ * 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.
+ */
+
+#ifndef __FOCUSABLE_H__
+#define __FOCUSABLE_H__
+
+#include "util.h"
+
+struct focusable {
+ WindowPtr win;
+ bool visible;
+ void *cookie;
+ bool modal;
+ short (*wait_type)(struct focusable *focusable);
+ 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);
+ bool (*menu)(struct focusable *focusable, short menu, short item);
+ void (*suspend)(struct focusable *focusable, EventRecord *event);
+ void (*resume)(struct focusable *focusable, EventRecord *event);
+ bool (*close)(struct focusable *focusable);
+ void (*atexit)(struct focusable *focusable);
+};
+extern struct focusable **focusables;
+extern short nfocusables;
+
+struct focusable * focusable_find(GrafPtr win);
+struct focusable * focusable_focused(void);
+void focusable_add(struct focusable *focusable);
+bool focusable_show(struct focusable *focusable);
+bool focusable_close(struct focusable *focusable);
+void focusable_hide(struct focusable *focusable);
+bool focusables_quit(void);
+
+#endif
--- MacTCP.h Wed Jul 13 17:14:33 2022
+++ MacTCP.h Wed Jul 13 17:14:33 2022
@@ -0,0 +1,877 @@
+/*
+ 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
+};
+
+enum {
+ ConnectionStateClosed = 0,
+ ConnectionStateListening = 2,
+ ConnectionStateSYNReceived = 4,
+ ConnectionStateSYNSent = 6,
+ ConnectionStateEstablished = 8,
+ ConnectionStateFINWait1 = 10,
+ ConnectionStateFINWait2 = 12,
+ ConnectionStateCloseWait = 14,
+ ConnectionStateClosing = 16,
+ ConnectionStateLastAck = 18,
+ ConnectionStateTimeWait = 20
+};
+
+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 struct UDPiopb UDPiopb;
+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;
+};
+
+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 Thu Aug 18 22:03:48 2022
+++ main.c Thu Aug 18 22:03:48 2022
@@ -0,0 +1,222 @@
+/*
+ * 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.
+ */
+
+#include <stdio.h>
+#include <string.h>
+
+#include "wikipedia.h"
+#include "browser.h"
+#include "focusable.h"
+#include "util.h"
+
+MenuHandle file_menu, edit_menu;
+bool quitting = false;
+
+void handle_menu(long menu_id);
+
+int
+main(void)
+{
+ Handle mbar;
+ MenuHandle apple_menu;
+ EventRecord event;
+ WindowPtr event_win;
+ GrafPtr old_port;
+ struct focusable *focusable;
+ short event_in, n;
+ char key;
+
+ InitGraf(&thePort);
+ InitFonts();
+ FlushEvents(everyEvent, 0);
+ InitWindows();
+ InitMenus();
+ TEInit();
+ InitDialogs(0);
+ InitCursor();
+ MaxApplZone();
+
+ mbar = GetNewMBar(MBAR_ID);
+ SetMenuBar(mbar);
+ apple_menu = GetMHandle(APPLE_MENU_ID);
+ AddResMenu(apple_menu, 'DRVR');
+ file_menu = GetMHandle(FILE_MENU_ID);
+ edit_menu = GetMHandle(EDIT_MENU_ID);
+ menu_defaults();
+ DrawMenuBar();
+
+ browser_init();
+
+ while (!quitting) {
+ WaitNextEvent(everyEvent, &event, 5L, 0L);
+
+ switch (event.what) {
+ case nullEvent:
+ for (n = 0; n < nfocusables; n++) {
+ if (focusables[n]->idle)
+ focusables[n]->idle(focusables[n], &event);
+ }
+ break;
+ case keyDown:
+ case autoKey:
+ key = event.message & charCodeMask;
+ if ((event.modifiers & cmdKey) != 0)
+ handle_menu(MenuKey(key));
+ else if ((focusable = focusable_focused()) &&
+ focusable->key_down)
+ focusable->key_down(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:
+ if ((focusable = focusable_find(event_win)) != NULL) {
+ if (!focusable_show(focusable))
+ break;
+
+ DragWindow(event_win, event.where, &screenBits.bounds);
+ }
+ break;
+ case inGoAway:
+ if (TrackGoAway(event_win, event.where)) {
+ if ((focusable = focusable_find(event_win)) != NULL)
+ focusable_close(focusable);
+ }
+ break;
+ case inContent:
+ if ((focusable = focusable_find(event_win)) != NULL) {
+ if (!focusable_show(focusable))
+ break;
+ if (focusable->mouse_down)
+ focusable->mouse_down(focusable, &event);
+ }
+ break;
+ }
+ break;
+ case updateEvt:
+ event_win = (WindowPtr)event.message;
+
+ GetPort(&old_port);
+ SetPort(event_win);
+ BeginUpdate(event_win);
+
+ focusable = focusable_find(event_win);
+ if (focusable && focusable->update)
+ focusable->update(focusable, &event);
+
+ EndUpdate(event_win);
+ SetPort(old_port);
+ break;
+ case activateEvt:
+ break;
+ case app4Evt:
+ if (HiWord(event.message) & (1 << 8)) {
+ /* multifinder suspend/resume */
+ switch (event.message & (1 << 0)) {
+ case 0:
+ /* suspend */
+ for (n = 0; n < nfocusables; n++) {
+ if (focusables[n]->suspend)
+ focusables[n]->suspend(focusables[n], &event);
+ }
+ break;
+ case 1:
+ /* resume */
+ for (n = 0; n < nfocusables; n++) {
+ if (focusables[n]->resume)
+ focusables[n]->resume(focusables[n], &event);
+ }
+ break;
+ }
+ }
+ break;
+ }
+ }
+
+ return 0;
+}
+
+void
+handle_menu(long menu_id)
+{
+ struct focusable *focused;
+ short menu, item;
+
+ menu = HiWord(menu_id);
+ item = LoWord(menu_id);
+
+ if ((focused = focusable_focused()) && focused->menu &&
+ focused->menu(focused, menu, item))
+ goto handled;
+
+ switch (menu) {
+ case APPLE_MENU_ID:
+ switch (item) {
+ 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);
+ snprintf(vers_s, sizeof(vers_s), "%s %s", PROGRAM_NAME,
+ short_vers);
+ ReleaseResource(vers);
+ note("%s", vers_s);
+ } else
+ warnx("Can't find version number!");
+ break;
+ }
+ }
+ break;
+ case FILE_MENU_ID:
+ switch (item) {
+ case FILE_MENU_QUIT_ID:
+ if (focusables_quit())
+ quitting = true;
+ break;
+ }
+ break;
+ }
+
+handled:
+ HiliteMenu(0);
+}
+
+void
+menu_defaults(void)
+{
+ DisableItem(edit_menu, EDIT_MENU_CUT_ID);
+ DisableItem(edit_menu, EDIT_MENU_COPY_ID);
+ DisableItem(edit_menu, EDIT_MENU_PASTE_ID);
+ DisableItem(edit_menu, EDIT_MENU_SELECT_ALL_ID);
+}
--- tcp.c Thu Aug 18 21:15:34 2022
+++ tcp.c Thu Aug 18 21:15:34 2022
@@ -0,0 +1,594 @@
+#include <stdio.h>
+#include <string.h>
+#include "tcp.h"
+
+#define RCV_BUFFER_SIZE 1024
+#define TCP_BUFFER_SIZE 8192
+#define OPEN_TIMEOUT 60
+
+/* define to enable SOCKS5 code */
+#undef SOCKS
+
+short gIPPDriverRefNum;
+
+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;
+
+ memset(&pb, 0, sizeof(pb));
+
+ pb.csCode = ipctlGetAddr;
+ pb.ioCRefNum = gIPPDriverRefNum;
+ pb.ioResult = 1;
+
+ 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->csCode = TCPCreate;
+ pb->ioCompletion = ioCompletion;
+ pb->ioCRefNum = gIPPDriverRefNum;
+ pb->ioResult = 1;
+
+ 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->csCode = TCPPassiveOpen;
+ pb->ioCompletion = ioCompletion;
+ pb->ioCRefNum = gIPPDriverRefNum;
+ pb->tcpStream = stream;
+ pb->ioResult = 1;
+
+ pb->csParam.open.ulpTimeoutAction = 1; /* abort half-open connection */
+ pb->csParam.open.ulpTimeoutValue = 5; /* after 5 seconds */
+ pb->csParam.open.validityFlags = 0xC0;
+ pb->csParam.open.commandTimeoutValue = 0;
+ pb->csParam.open.remoteHost = 0;
+ pb->csParam.open.remotePort = 0;
+ pb->csParam.open.localHost = 0;
+ pb->csParam.open.localPort = *localPort;
+ pb->csParam.open.tosFlags = 0x1; /* low delay */
+ 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;
+ pb->csParam.open.userDataPtr = userData;
+
+ osErr = PBControl((ParmBlkPtr) pb, async);
+ if (!async && (osErr == noErr)) {
+ if (remoteIP)
+ *remoteIP = pb->csParam.open.remoteHost;
+ if (remotePort)
+ *remotePort = pb->csParam.open.remotePort;
+ if (localIP)
+ *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->csCode = TCPActiveOpen;
+ pb->ioCompletion = ioCompletion;
+ pb->ioCRefNum = gIPPDriverRefNum;
+ pb->tcpStream = stream;
+ pb->ioResult = 1;
+
+ 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->csCode = TCPSend;
+ pb->ioCompletion = ioCompletion;
+ pb->ioCRefNum = gIPPDriverRefNum;
+ pb->tcpStream = stream;
+ pb->ioResult = 1;
+
+ 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)
+{
+ memset(pb, 0, sizeof(*pb));
+
+ pb->csCode = TCPNoCopyRcv;
+ pb->ioCompletion = ioCompletion;
+ pb->ioCRefNum = gIPPDriverRefNum;
+ pb->tcpStream = stream;
+ pb->ioResult = 1;
+
+ 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->csCode = TCPRcv;
+ pb->ioCompletion = ioCompletion;
+ pb->ioCRefNum = gIPPDriverRefNum;
+ pb->tcpStream = stream;
+ pb->ioResult = 1;
+
+ 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->csCode = TCPRcvBfrReturn;
+ pb->ioCompletion = ioCompletion;
+ pb->ioCRefNum = gIPPDriverRefNum;
+ pb->tcpStream = stream;
+ pb->ioResult = 1;
+
+ 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->csCode = TCPClose;
+ pb->ioCompletion = ioCompletion;
+ pb->ioCRefNum = gIPPDriverRefNum;
+ pb->tcpStream = stream;
+ pb->ioResult = 1;
+
+ 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->csCode = TCPAbort;
+ pb->ioCompletion = ioCompletion;
+ pb->ioCRefNum = gIPPDriverRefNum;
+ pb->tcpStream = stream;
+ pb->ioResult = 1;
+
+ 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->csCode = TCPStatus;
+ pb->ioCompletion = ioCompletion;
+ pb->ioCRefNum = gIPPDriverRefNum;
+ pb->tcpStream = stream;
+ pb->csParam.status.userDataPtr = userData;
+ pb->ioResult = 1;
+
+ 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->csCode = TCPRelease;
+ pb->ioCompletion = ioCompletion;
+ pb->ioCRefNum = gIPPDriverRefNum;
+ pb->tcpStream = stream;
+ pb->ioResult = 1;
+
+ pb->csParam.status.userDataPtr = userData;
+
+ osErr = PBControl((ParmBlkPtr)pb, async);
+
+ return osErr;
+}
+
+OSErr
+_UDPMaxMTUSize(UDPiopb *pb, short *mtu)
+{
+ OSErr osErr;
+
+ memset(pb, 0, sizeof(*pb));
+
+ pb->csCode = UDPMaxMTUSize;
+ pb->ioCRefNum = gIPPDriverRefNum;
+ pb->ioResult = 1;
+
+ pb->csParam.mtu.remoteHost = (ip_addr)0;
+
+ osErr = PBControl((ParmBlkPtr)pb, false);
+
+ if (osErr == noErr)
+ *mtu = pb->csParam.mtu.mtuSize;
+
+ return osErr;
+}
+
+OSErr
+_UDPCreate(UDPiopb *pb, StreamPtr *stream, Ptr rcvBufPtr, long rcvBufLen,
+ UDPNotifyProc aNotifyProc, Ptr userDataPtr,
+ UDPIOCompletionProc ioCompletion, Boolean async)
+{
+ OSErr osErr;
+
+ memset(pb, 0, sizeof(*pb));
+
+ pb->csCode = UDPCreate;
+ pb->ioCompletion = ioCompletion;
+ pb->ioCRefNum = gIPPDriverRefNum;
+ pb->ioResult = 1;
+
+ 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->udpStream;
+ }
+
+ return osErr;
+}
+
+OSErr
+_UDPSend(UDPiopb *pb, StreamPtr stream, wdsEntry *wdsPtr, ip_addr remoteIP,
+ udp_port remotePort, Ptr userData, UDPIOCompletionProc ioCompletion,
+ Boolean async)
+{
+ memset(pb, 0, sizeof(*pb));
+
+ pb->csCode = UDPWrite;
+ pb->ioCompletion = ioCompletion;
+ pb->ioCRefNum = gIPPDriverRefNum;
+ pb->udpStream = stream;
+ pb->ioResult = 1;
+
+ pb->csParam.send.remoteHost = remoteIP;
+ pb->csParam.send.remotePort = remotePort;
+ pb->csParam.send.wdsPtr = (Ptr)wdsPtr;
+ pb->csParam.send.checkSum = 0;
+ pb->csParam.send.sendLength = 0;
+ pb->csParam.send.userDataPtr = userData;
+
+ return PBControl((ParmBlkPtr)pb, async);
+}
+
+OSErr
+_UDPRelease(UDPiopb *pb, StreamPtr stream, Ptr userData,
+ UDPIOCompletionProc ioCompletion, Boolean async)
+{
+ OSErr osErr;
+
+ memset(pb, 0, sizeof(*pb));
+
+ pb->csCode = UDPRelease;
+ pb->ioCompletion = ioCompletion;
+ pb->ioCRefNum = gIPPDriverRefNum;
+ pb->udpStream = stream;
+ pb->ioResult = 1;
+
+ //pb->csParam.status.userDataPtr = userData;
+
+ osErr = PBControl((ParmBlkPtr)pb, async);
+
+ return osErr;
+}
+
+
+/* convenience functions */
+
+unsigned long
+ip2long(char *ip)
+{
+ unsigned long address = 0;
+ short dotcount = 0, i;
+ unsigned short 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 *)#
+ sprintf(ip, "%d.%d.%d.%d", tmp[0], tmp[1], tmp[2], tmp[3]);
+}
+
+#ifdef SOCKS
+#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;
+}
+#endif /* SOCKS */
--- tcp.h Thu Aug 18 21:08:51 2022
+++ tcp.h Thu Aug 18 21:08:51 2022
@@ -0,0 +1,70 @@
+/*
+ * 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 ProcPtr UDPNotifyProc;
+typedef void (*TCPIOCompletionProc)(struct TCPiopb *iopb);
+typedef void (*UDPIOCompletionProc)(struct UDPiopb *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 _UDPMaxMTUSize(UDPiopb *pb, short *mtu);
+OSErr _UDPCreate(UDPiopb *pb, StreamPtr *stream, Ptr rcvBufPtr,
+ long rcvBufLen, UDPNotifyProc aNotifyProc, Ptr userDataPtr,
+ UDPIOCompletionProc ioCompletion, Boolean async);
+OSErr _UDPSend(UDPiopb *pb, StreamPtr stream, wdsEntry *wdsPtr,
+ ip_addr remoteIP, udp_port remotePort, Ptr userData,
+ UDPIOCompletionProc ioCompletion, Boolean async);
+OSErr _UDPRelease(UDPiopb *pb, StreamPtr stream, Ptr userData,
+ UDPIOCompletionProc ioCompletion, Boolean async);
+
+unsigned 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 Thu Aug 18 23:17:52 2022
+++ util.c Thu Aug 18 23:17:52 2022
@@ -0,0 +1,1523 @@
+/*
+ * 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 <GestaltEqu.h>
+#include <Script.h>
+#include <SetUpA4.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 const 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 const 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 const 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;
+
+static TEHandle track_control_te = NULL;
+
+enum {
+ STOP_ALERT,
+ CAUTION_ALERT,
+ NOTE_ALERT
+};
+
+#ifdef MALLOC_DEBUG
+
+/*
+ * List of allocations, updated at xmalloc() and xfree(). If an address
+ * passed to xfree() isn't in the list, it indicates a double-free.
+ */
+#define MALLOC_MAP_SIZE 512
+struct malloc_map_e {
+ unsigned long addr;
+ unsigned long size;
+ char note[MALLOC_NOTE_SIZE];
+} malloc_map[MALLOC_MAP_SIZE];
+
+/*
+ * On xfree(&), the pointer will be updated to point here. xfree_verify()
+ * should be called periodically to detect any writes to it, indicating
+ * a use-after-free.
+ */
+#define MALLOC_UAF_SIZE 256
+static const char malloc_uaf_zero[MALLOC_UAF_SIZE] = { 0 };
+static char malloc_uaf[MALLOC_UAF_SIZE];
+
+static bool malloc_map_compact = false;
+
+#endif
+
+void vwarn(short alert_func, const char *format, va_list ap);
+
+/*
+ * Util helper needed to be called at program startup, to pre-allocate
+ * some things that we can't do during errors.
+ */
+
+void
+util_init(void)
+{
+ alert_ditl_h = xNewHandle(sizeof(alert_ditl));
+ HLock(alert_ditl_h);
+ memcpy(*alert_ditl_h, alert_ditl, sizeof(alert_ditl));
+ HUnlock(alert_ditl_h);
+
+#ifdef MALLOC_DEBUG
+ memset(&malloc_map, 0, sizeof(malloc_map));
+ memset(&malloc_uaf, 0, sizeof(malloc_uaf));
+#endif
+}
+
+/*
+ * Memory functions
+ */
+
+#define MUL_NO_OVERFLOW ((size_t)1 << (sizeof(size_t) * 4))
+
+void *
+xmalloc(size_t size, char *note)
+{
+ void *ptr;
+#ifdef MALLOC_DEBUG
+ unsigned short n, j;
+#endif
+
+ if (size == 0)
+ panic("xmalloc: zero size");
+
+ ptr = NewPtr(size);
+ if (ptr == NULL)
+ panic("xmalloc(%lu) failed", size);
+
+#ifdef MALLOC_DEBUG
+ if (malloc_map_compact) {
+ for (n = 0; n < MALLOC_MAP_SIZE; n++) {
+ if (malloc_map[n].addr != 0)
+ continue;
+
+ for (j = n + 1; j < MALLOC_MAP_SIZE; j++) {
+ if (malloc_map[j].addr == 0)
+ continue;
+
+ malloc_map[n] = malloc_map[j];
+ memset(&malloc_map[j], 0, sizeof(malloc_map[j]));
+ break;
+ }
+ }
+
+ malloc_map_compact = false;
+ }
+
+ for (n = 0; n <= MALLOC_MAP_SIZE; n++) {
+ if (n == MALLOC_MAP_SIZE)
+ panic("xmalloc(%lu): out of malloc map entries, likely a "
+ "memory leak", size);
+ if (malloc_map[n].addr == 0) {
+ malloc_map[n].addr = (unsigned long)ptr;
+ malloc_map[n].size = size;
+ strlcpy(malloc_map[n].note, note, sizeof(malloc_map[n].note));
+ break;
+ }
+ n = n;
+ }
+#endif
+
+ return ptr;
+}
+
+void
+xfree(void *ptrptr)
+{
+ unsigned long *addr = (unsigned long *)ptrptr;
+ void *ptr = (void *)*addr;
+#ifdef MALLOC_DEBUG
+ unsigned long n;
+
+ if (*addr == (unsigned long)&malloc_uaf)
+ panic("xfree(%lu): double free()", *addr);
+
+ for (n = 0; n <= MALLOC_MAP_SIZE; n++) {
+ if (n == MALLOC_MAP_SIZE)
+ panic("xfree(%lu): can't find in alloc map, likely "
+ "double free()", *addr);
+ if (malloc_map[n].addr == *addr) {
+ malloc_map[n].addr = 0;
+ malloc_map[n].size = 0;
+ malloc_map[n].note[0] = '\0';
+ break;
+ }
+ }
+#endif
+
+ DisposePtr(ptr);
+
+#ifdef MALLOC_DEBUG
+ *addr = (unsigned long)&malloc_uaf;
+#else
+ *addr = 0L;
+#endif
+}
+
+#ifdef MALLOC_DEBUG
+void
+xfree_verify(void)
+{
+ if (memcmp(malloc_uaf, malloc_uaf_zero, sizeof(malloc_uaf)) != 0)
+ panic("xfree_verify: use-after-free detected");
+}
+#endif
+
+void *
+xmalloczero(size_t size, char *note)
+{
+ void *ptr;
+
+ ptr = xmalloc(size, note);
+ memset(ptr, 0, size);
+
+ return ptr;
+}
+
+void *
+xcalloc(size_t nmemb, size_t size, char *note)
+{
+ void *ptr;
+
+ if ((nmemb >= MUL_NO_OVERFLOW || size >= MUL_NO_OVERFLOW) &&
+ nmemb > 0 && SIZE_MAX / nmemb < size)
+ panic("xcalloc(%lu, %lu) overflow", nmemb, size);
+ ptr = xmalloczero(nmemb * size, note);
+ if (ptr == NULL)
+ panic("xcalloc(%lu, %lu) failed", nmemb, size);
+
+ return ptr;
+}
+
+void *
+xrealloc(void *src, size_t size)
+{
+ void *ptr, *tsrc;
+ unsigned long n;
+ char note[MALLOC_NOTE_SIZE] = "realloc from null";
+
+#ifdef MALLOC_DEBUG
+ if (src != NULL) {
+ for (n = 0; n <= MALLOC_MAP_SIZE; n++) {
+ if (n == MALLOC_MAP_SIZE) {
+ panic("xrealloc(%lu): can't find in alloc map, likely "
+ "double free()", (unsigned long)src);
+ return NULL;
+ }
+ if (malloc_map[n].addr == (unsigned long)src) {
+ strlcpy(note, malloc_map[n].note, sizeof(note));
+ break;
+ }
+ }
+ }
+#endif
+
+ ptr = xmalloc(size, note);
+ if (src != NULL) {
+ memcpy(ptr, src, size);
+ tsrc = src;
+ xfree(&tsrc);
+ }
+
+ return ptr;
+}
+
+void *
+xreallocarray(void *optr, size_t nmemb, size_t size)
+{
+ 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 *note)
+{
+ char *cp;
+ size_t len;
+
+ len = strlen(str);
+
+ cp = xmalloc(len + 1, note);
+ 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, "getline");
+ 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";
+ }
+ }
+}
+
+size_t
+rtrim(char *str, char *chars)
+{
+ size_t len, rlen, n, j;
+
+ rlen = len = strlen(str);
+
+ for (n = len; n > 0; n--) {
+ for (j = 0; chars[j] != '\0'; j++) {
+ if (str[n - 1] == chars[j]) {
+ rlen--;
+ str[n - 1] = '\0';
+ goto next_in_str;
+ }
+ }
+
+ break;
+next_in_str:
+ continue;
+ }
+
+ return rlen;
+}
+
+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;
+ short height, width, hit;
+ WindowPtr win, dialog;
+
+ 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");
+
+ 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);
+
+ /*
+ * Pre-allocate for the next one while we have memory. We can't use
+ * xNewHandle because we might already be alerting about an OOM
+ * condition.
+ */
+ DisposHandle(alert_ditl_h);
+ alert_ditl_h = NewHandle(sizeof(alert_ditl));
+ if (alert_ditl_h == NULL)
+ ExitToShell();
+ HLock(alert_ditl_h);
+ memcpy(*alert_ditl_h, alert_ditl, sizeof(alert_ditl));
+ HUnlock(alert_ditl_h);
+
+ SetPort(win);
+}
+
+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;
+ short height, width, hit;
+ WindowPtr win, dialog;
+ 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(ask_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;
+ short ttype;
+
+ if (format == NULL) {
+ if (progress_dialog != NULL) {
+ DisposDialog(progress_dialog);
+ DisposHandle(progress_ditl_h);
+ progress_dialog = NULL;
+ }
+ return;
+ }
+
+ va_start(argptr, format);
+ vsnprintf((char *)progress_s, 256, format, argptr);
+ va_end(argptr);
+ CtoPstr(progress_s);
+
+ if (progress_dialog == NULL) {
+ 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);
+}
+
+void
+window_rect(WindowPtr win, Rect *ret)
+{
+ *ret = (*(((WindowPeek)win)->strucRgn))->rgnBBox;
+ ret->left += 1;
+ ret->right -= 2;
+ ret->top -= 1;
+ ret->bottom -= 1;
+}
+
+/*
+ * 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;
+ char *out;
+ size_t l;
+
+ h = xGetString(id);
+ HLock(h);
+ l = (*h)[0];
+ out = xmalloc(l + 1, "xGetStringAsChar");
+ memcpy((void *)out, (void *)(*h + 1), l);
+ out[l] = '\0';
+ ReleaseResource(h);
+
+ return out;
+}
+
+long
+xGetStringAsLong(short id)
+{
+ char *c;
+ long r;
+
+ c = xGetStringAsChar(id);
+ r = atol(c);
+ xfree(&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;
+ char *name = NULL;
+ size_t retlen = 0, len;
+ char *tmpret = NULL, *tmp = NULL;
+ 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;
+ }
+
+ name = xmalloc(FILENAME_MAX, "getpath");
+
+ 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");
+ xfree(&name);
+ return 1;
+ }
+
+ wvol.ioNamePtr = (StringPtr)name;
+ wvol.ioVRefNum = vRefNum;
+ wvol.ioVolIndex = 0;
+
+ if (PBHGetVInfoSync((HParmBlkPtr)&wvol) != noErr) {
+ warn("Failed getting volume info");
+ xfree(&name);
+ return 1;
+ }
+
+ if (wvol.ioVSigWord != 0x4244) {
+ warn("Unknown filesystem type 0x%x", wvol.ioVSigWord);
+ xfree(&name);
+ return 1;
+ }
+
+ wcinfo.ioVRefNum = vRefNum;
+ wcinfo.ioNamePtr = (StringPtr)name;
+ wcinfo.ioFDirIndex = -1;
+ wcinfo.ioDrParID = wdir.ioWDDirID;
+ wcinfo.ioDrDirID = wdir.ioWDDirID;
+
+ tmp = xmalloc(FILENAME_MAX, "getpath");
+ tmpret = xmalloc(FILENAME_MAX, "getpath");
+
+ /* 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, FILENAME_MAX);
+ } else {
+ strlcpy(tmp, tmpret, FILENAME_MAX);
+ snprintf(tmpret, FILENAME_MAX, "%s:%s", name, tmp);
+ }
+ }
+
+ if (include_file) {
+ /* append the original path */
+ memcpy(name, fileName, FILENAME_MAX);
+ PtoCstr(name);
+ if (retlen == 0)
+ strlcpy(tmpret, name, FILENAME_MAX);
+ else {
+ strlcat(tmpret, ":", FILENAME_MAX);
+ strlcat(tmpret, name, FILENAME_MAX);
+ }
+ } else if (retlen == 0) {
+ (*ret)[0] = 0;
+ xfree(&tmp);
+ xfree(&tmpret);
+ xfree(&name);
+ return 0;
+ }
+
+ CtoPstr(tmpret);
+ memcpy(*ret, tmpret, FILENAME_MAX);
+ xfree(&tmp);
+ xfree(&tmpret);
+ xfree(&name);
+
+ return 0;
+}
+
+short
+stat(char *path, struct stat *sb)
+{
+ char *ppath;
+ short ret;
+
+ ppath = xstrdup(path, "stat");
+ CtoPstr(ppath);
+ ret = FStat((unsigned char *)ppath, sb);
+ xfree(&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 (FStat(path, &st) != 0)
+ return false;
+
+ /* 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;
+ 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;
+
+ buf = xmalloc(1024, "copy_file_contents");
+
+ while (source_size > 0) {
+ count = 1024;
+ 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;
+ }
+
+ xfree(&buf);
+
+ 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;
+
+ 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;
+}
+
+
+/*
+ * Gestalt functions
+ */
+char *
+gestalt_machine_type(void)
+{
+ short error;
+ long resp;
+
+ error = Gestalt(gestaltMachineType, &resp);
+ if (error)
+ return NULL;
+ switch (resp) {
+ case gestaltClassic:
+ return "Macintosh 128K";
+ case gestaltMacXL:
+ return "Macintosh XL";
+ case gestaltMac512KE:
+ return "Macintosh 512Ke";
+ case gestaltMacPlus:
+ return "Macintosh Plus";
+ case gestaltMacSE:
+ return "Macintosh SE";
+ case gestaltMacII:
+ return "Macintosh II";
+ case gestaltMacIIx:
+ return "Macintosh IIx";
+ case gestaltMacIIcx:
+ return "Macintosh IIcx";
+ case gestaltMacSE030:
+ return "Macintosh SE/30";
+ case gestaltPortable:
+ return "Macintosh Portable";
+ case gestaltMacIIci:
+ return "Macintosh IIci";
+ case gestaltMacIIfx:
+ return "Macintosh IIfx";
+ case gestaltMacClassic:
+ return "Macintosh Classic";
+ case gestaltMacIIsi:
+ return "Macintosh IIsi";
+ case gestaltMacLC:
+ return "Macintosh LC";
+ }
+
+ return NULL;
+}
+
+char *
+get_version(bool long_version)
+{
+ static char vers_s[256] = { 0 };
+ char *v;
+ VersRecHndl vers;
+ short len;
+
+ vers = (VersRecHndl)GetResource('vers', 1);
+ if (!vers)
+ return "?.?";
+
+ HLock(vers);
+ v = (char *)&(*vers)->shortVersion;
+ len = v[0];
+ if (long_version) {
+ v += len + 1;
+ len = v[0];
+ }
+ memcpy(vers_s, v, len + 1);
+ ReleaseResource(vers);
+
+ PtoCstr(vers_s);
+ return vers_s;
+}
+
+/*
+ * 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_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;
+ }
+ }
+ }
+
+ /*
+ * Avoid SetCtlMax because it will redraw and then SetCtlValue will
+ * redraw again, which can cause a jump if we're trying to keep the
+ * scrollbar position in the same place (like at the bottom).
+ */
+ (*control)->contrlMax = 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)
+{
+ WindowPtr event_win;
+ short event_in;
+ char key;
+
+ switch (event->what) {
+ case mouseDown:
+ event_in = FindWindow(event->where, &event_win);
+
+ switch (event_in) {
+ 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;
+}
+
+static short _password_dialog_ditl_id = -1;
+static char *_password_dialog_storage = NULL;
+static size_t _password_dialog_storage_len = 0;
+
+void
+PasswordDialogFieldFilterSetup(short ditl_id, char *storage, size_t len)
+{
+ _password_dialog_ditl_id = ditl_id;
+ _password_dialog_storage = storage;
+ _password_dialog_storage_len = len;
+
+ memset(_password_dialog_storage, 0, len);
+}
+
+pascal bool
+PasswordDialogFieldFilter(DialogPtr dlg, EventRecord *event, short *hit)
+{
+ DialogPeek dlgp;
+ short sel_start, sel_end;
+ char key;
+
+ dlgp = (DialogPeek)dlg;
+ if (dlgp->editField == _password_dialog_ditl_id - 1) {
+ sel_start = (*(dlgp->textH))->selStart;
+ sel_end = (*(dlgp->textH))->selEnd;
+
+ switch (event->what) {
+ case keyDown:
+ case autoKey:
+ key = event->message & charCodeMask;
+ if (event->modifiers & cmdKey) {
+ /* TODO: implement DlgPaste for cmd+v? */
+ event->what = nullEvent;
+ return false;
+ }
+
+ if (key == 8) {
+ /* backspace */
+ if (sel_start == sel_end && sel_start > 0)
+ memmove(_password_dialog_storage + sel_start - 1,
+ _password_dialog_storage + sel_start,
+ _password_dialog_storage_len - sel_start - 1);
+ else if (sel_start != sel_end)
+ memmove(_password_dialog_storage + sel_start,
+ _password_dialog_storage + sel_end,
+ _password_dialog_storage_len - sel_end - 1);
+ } else if (sel_start >= _password_dialog_storage_len) {
+ event->what = nullEvent;
+ return false;
+ } else if (key >= ' ' && key <= '~') {
+ if (sel_start != sel_end)
+ /* delete selection before making space for new char */
+ memmove(_password_dialog_storage + sel_start,
+ _password_dialog_storage + sel_end,
+ _password_dialog_storage_len - sel_end - 1);
+ memmove(_password_dialog_storage + sel_start + 1,
+ _password_dialog_storage + sel_start,
+ _password_dialog_storage_len - sel_start - 1);
+ _password_dialog_storage[sel_start] = key;
+ event->message = '•';
+ }
+ _password_dialog_storage[(*(dlgp->textH))->teLength + 1] = '\0';
+ sel_start = 0;
+ break;
+ }
+ }
+
+ return ModalDialogFilter(dlg, event, hit);
+}
+
+void
+PasswordDialogFieldFinish(void)
+{
+ _password_dialog_ditl_id = -1;
+ _password_dialog_storage = NULL;
+ _password_dialog_storage_len = 0;
+}
+
+/* (*(some_te))->caretHook = NullCaretHook; */
+pascal void
+NullCaretHook(void)
+{
+ asm {
+ move.l (a7)+,d0
+ rts
+ }
+}
+
+static bool menu_bar_hidden = false;
+static short old_mbar_height;
+static Rect mbar_rect;
+static RgnHandle mbar_save_region;
+
+void
+HideMenuBar(void)
+{
+ RgnHandle mbar_region;
+ WindowPeek win;
+
+ old_mbar_height = GetMBarHeight();
+ SetRect(&mbar_rect, screenBits.bounds.left, screenBits.bounds.top,
+ screenBits.bounds.right, screenBits.bounds.top + old_mbar_height);
+
+ mbar_save_region = NewRgn();
+ mbar_region = NewRgn();
+
+ MBarHeight = 0;
+ CopyRgn(GetGrayRgn(), mbar_save_region);
+ RectRgn(mbar_region, &mbar_rect);
+ UnionRgn(GetGrayRgn(), mbar_region, GetGrayRgn());
+
+ win = (WindowPeek)FrontWindow();
+ PaintOne(win, mbar_region);
+ PaintBehind(win, mbar_region);
+ CalcVis(win);
+ CalcVisBehind(win, mbar_region);
+ DisposeRgn(mbar_region);
+
+ menu_bar_hidden = true;
+}
+
+void
+RestoreHiddenMenuBar(void)
+{
+ WindowPeek win;
+
+ if (!menu_bar_hidden)
+ return;
+
+ CopyRgn(mbar_save_region, GetGrayRgn());
+ MBarHeight = old_mbar_height;
+
+ RectRgn(mbar_save_region, &mbar_rect);
+ win = (WindowPeek)FrontWindow();
+ CalcVis(win);
+ CalcVisBehind(win, mbar_save_region);
+ DisposeRgn(mbar_save_region);
+
+ menu_bar_hidden = false;
+
+ HiliteMenu(0);
+ DrawMenuBar();
+}
--- util.h Thu Aug 18 22:36:49 2022
+++ util.h Thu Aug 18 22:36:49 2022
@@ -0,0 +1,158 @@
+/*
+ * 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>
+
+#define MALLOC_DEBUG
+#define MALLOC_NOTE_SIZE 32
+
+#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 CHARS_TO_LONG(a,b,c,d) (unsigned long)(\
+ ((unsigned long)((unsigned char)(a)) << 24) | \
+ ((unsigned long)((unsigned char)(b)) << 16) | \
+ ((unsigned long)((unsigned char)(c)) << 8) | \
+ (unsigned long)((unsigned char)(d)) )
+#define CHARS_TO_SHORT(a,b) (unsigned short)(\
+ ((unsigned short)((unsigned char)(a)) << 8) | \
+ (unsigned short)((unsigned char)(b)) )
+
+#define SCROLLBAR_WIDTH 16
+
+/* GetMBarHeight() is not very useful */
+#define MENUBAR_HEIGHT 20
+
+#define TICKS_PER_SEC 60L
+
+#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 unsigned char u_int8_t;
+typedef unsigned short u_int16_t;
+typedef unsigned long u_int32_t;
+
+#define BYTE_ORDER BIG_ENDIAN
+
+typedef struct {
+ short push[2], rts;
+ void *addr;
+} tCodeStub;
+
+struct stat {
+ short st_mode;
+ ssize_t st_size;
+ time_t st_ctime;
+ time_t st_mtime;
+ unsigned char st_flags;
+};
+
+void util_init(void);
+
+void * xmalloc(size_t, char *note);
+void xfree(void *ptrptr);
+void xfree_verify(void);
+void * xmalloczero(size_t, char *note);
+void * xcalloc(size_t, size_t, char *note);
+void * xrealloc(void *src, size_t size);
+void * xreallocarray(void *, size_t, size_t);
+char * xstrdup(const char *, char *note);
+
+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);
+size_t rtrim(char *str, char *chars);
+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, ...);
+void window_rect(WindowPtr win, Rect *ret);
+
+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);
+
+char * gestalt_machine_type(void);
+char * get_version(bool long_version);
+
+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);
+void PasswordDialogFieldFilterSetup(short ditl_id, char *storage,
+ size_t len);
+pascal bool PasswordDialogFieldFilter(DialogPtr dlg, EventRecord *event,
+ short *hit);
+void PasswordDialogFieldFinish(void);
+pascal void NullCaretHook(void);
+void HideMenuBar(void);
+void RestoreHiddenMenuBar(void);
+
+#endif
--- wikipedia.h Thu Aug 18 21:06:48 2022
+++ wikipedia.h Thu Aug 18 21:06:48 2022
@@ -0,0 +1,35 @@
+/*
+ * 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.
+ */
+
+#define PROGRAM_NAME "Wikipedia"
+
+#define MBAR_ID 128
+
+#define APPLE_MENU_ID 128
+#define APPLE_MENU_ABOUT_ID 1
+
+#define FILE_MENU_ID 129
+#define FILE_MENU_QUIT_ID 1
+
+#define EDIT_MENU_ID 130
+#define EDIT_MENU_CUT_ID 1
+#define EDIT_MENU_COPY_ID 2
+#define EDIT_MENU_PASTE_ID 3
+#define EDIT_MENU_SELECT_ALL_ID 4
+
+extern MenuHandle file_menu, edit_menu;
+
+void menu_defaults(void);