source: trunk/src/kernel32/hmdevio.cpp@ 7245

Last change on this file since 7245 was 7245, checked in by sandervl, 24 years ago

RegisterCustomDriver added

File size: 20.4 KB
Line 
1/* $Id: hmdevio.cpp,v 1.15 2001-10-28 12:48:04 sandervl Exp $ */
2
3/*
4 * Win32 Device IOCTL API functions for OS/2
5 *
6 * Copyright 1998 Sander van Leeuwen
7 *
8 *
9 * Project Odin Software License can be found in LICENSE.TXT
10 *
11 */
12#define INCL_DOSPROFILE
13#define INCL_DOSDEVICES
14#define INCL_DOSDEVIOCTL
15#define INCL_GPI
16#define INCL_DOSFILEMGR /* File Manager values */
17#define INCL_DOSERRORS /* DOS Error values */
18#define INCL_DOSPROCESS /* DOS Process values */
19#define INCL_DOSMISC /* DOS Miscellanous values */
20#include <os2wrap.h> //Odin32 OS/2 api wrappers
21#include <string.h>
22#include <stdio.h>
23
24#include <win32type.h>
25#include <win32api.h>
26#include <misc.h>
27#include <win\winioctl.h>
28#include "hmdevio.h"
29#include "cio.h"
30#include "map.h"
31#include "exceptutil.h"
32
33#define DBG_LOCALLOG DBG_hmdevio
34#include "dbglocal.h"
35
36static BOOL fX86Init = FALSE;
37//SvL: Used in iccio.asm (how can you put these in the .asm data segment without messing things up?)
38ULONG ioentry = 0;
39USHORT gdt = 0;
40char devname[] = "/dev/fastio$";
41
42static BOOL GpdDevIOCtl(HANDLE hDevice, DWORD dwIoControlCode, LPVOID lpInBuffer, DWORD nInBufferSize, LPVOID lpOutBuffer, DWORD nOutBufferSize, LPDWORD lpBytesReturned, LPOVERLAPPED lpOverlapped);
43static BOOL MAPMEMIOCtl(HANDLE hDevice, DWORD dwIoControlCode, LPVOID lpInBuffer, DWORD nInBufferSize, LPVOID lpOutBuffer, DWORD nOutBufferSize, LPDWORD lpBytesReturned, LPOVERLAPPED lpOverlapped);
44static BOOL FXMEMMAPIOCtl(HANDLE hDevice, DWORD dwIoControlCode, LPVOID lpInBuffer, DWORD nInBufferSize, LPVOID lpOutBuffer, DWORD nOutBufferSize, LPDWORD lpBytesReturned, LPOVERLAPPED lpOverlapped);
45static BOOL VPCIOCtl(HANDLE hDevice, DWORD dwIoControlCode, LPVOID lpInBuffer, DWORD nInBufferSize, LPVOID lpOutBuffer, DWORD nOutBufferSize, LPDWORD lpBytesReturned, LPOVERLAPPED lpOverlapped);
46
47static WIN32DRV knownDriver[] =
48 {{"\\\\.\\GpdDev", "", TRUE, 666, GpdDevIOCtl},
49 { "\\\\.\\MAPMEM", "PMAP$", FALSE, 0, MAPMEMIOCtl},
50 { "FXMEMMAP.VXD", "PMAP$", FALSE, 0, FXMEMMAPIOCtl},
51#if 1
52 { "\\\\.\\VPCAppSv", "", TRUE, 667, VPCIOCtl}};
53#else
54 };
55#endif
56
57static int nrKnownDrivers = sizeof(knownDriver)/sizeof(WIN32DRV);
58BOOL fVirtualPC = FALSE;
59
60//******************************************************************************
61//******************************************************************************
62void RegisterDevices()
63{
64 HMDeviceDriver *driver;
65 DWORD rc;
66
67 for(int i=0;i<nrKnownDrivers;i++)
68 {
69 driver = new HMDeviceDriver(knownDriver[i].szWin32Name,
70 knownDriver[i].szOS2Name,
71 knownDriver[i].fCreateFile,
72 knownDriver[i].devIOCtl);
73
74 rc = HMDeviceRegister(knownDriver[i].szWin32Name, driver);
75 if (rc != NO_ERROR) /* check for errors */
76 dprintf(("KERNEL32:RegisterDevices: registering %s failed with %u.\n",
77 knownDriver[i].szWin32Name, rc));
78 }
79
80 //check registry for Odin driver plugin dlls
81 HKEY hkDrivers, hkDrvDll;
82
83 rc = RegOpenKeyExA(HKEY_LOCAL_MACHINE,
84 "System\\CurrentControlSet\\Services",
85 0, KEY_READ, &hkDrivers);
86
87 if(rc == 0) {
88 char szDllName[CCHMAXPATH];
89 char szKeyName[CCHMAXPATH];
90 char szDrvName[CCHMAXPATH];
91 DWORD dwType, dwSize;
92 int iSubKey = 0;
93
94 while(rc == 0) {
95 rc = RegEnumKeyA(hkDrivers, iSubKey++, szKeyName, sizeof(szKeyName));
96 if(rc) break;
97
98 rc = RegOpenKeyExA(hkDrivers, szKeyName,
99 0, KEY_READ, &hkDrvDll);
100 if(rc == 0) {
101 dwSize = sizeof(szDllName);
102 rc = RegQueryValueExA(hkDrvDll,
103 "DllName",
104 NULL,
105 &dwType,
106 (LPBYTE)szDllName,
107 &dwSize);
108
109 RegCloseKey(hkDrvDll);
110 if(rc == 0 && dwType == REG_SZ)
111 {
112 HINSTANCE hDrvDll = LoadLibraryA(szDllName);
113 if(hDrvDll) {
114 sprintf(szDrvName, "\\\\.\\%s", szKeyName);
115 driver = new HMCustomDriver(hDrvDll, szDrvName);
116
117 rc = HMDeviceRegister(szDrvName, driver);
118 if (rc != NO_ERROR) /* check for errors */
119 dprintf(("KERNEL32:RegisterDevices: registering %s failed with %u.\n", szDrvName, rc));
120 }
121 }
122 rc = 0;
123 }
124 }
125 RegCloseKey(hkDrivers);
126 }
127
128 return;
129}
130//******************************************************************************
131//******************************************************************************
132BOOL WIN32API RegisterCustomDriver(PFNDRVOPEN pfnDriverOpen, PFNDRVCLOSE pfnDriverClose,
133 PFNDRVIOCTL pfnDriverIOCtl, LPCSTR lpDeviceName)
134{
135 HMDeviceDriver *driver;
136 DWORD rc;
137
138 driver = new HMCustomDriver(pfnDriverOpen, pfnDriverClose, pfnDriverIOCtl, lpDeviceName);
139 if(driver == NULL) {
140 DebugInt3();
141 return FALSE;
142 }
143 rc = HMDeviceRegister((LPSTR)lpDeviceName, driver);
144 if (rc != NO_ERROR) { /* check for errors */
145 dprintf(("KERNEL32:RegisterDevices: registering %s failed with %u.\n", lpDeviceName, rc));
146 return FALSE;
147 }
148 return TRUE;
149}
150//******************************************************************************
151//******************************************************************************
152HMDeviceDriver::HMDeviceDriver(LPCSTR lpDeviceName, LPSTR lpOS2DevName, BOOL fCreate,
153 WINIOCTL pDevIOCtl)
154 : HMDeviceKernelObjectClass(lpDeviceName)
155{
156 this->fCreateFile = fCreateFile;
157 this->szOS2Name = lpOS2DevName;
158 this->devIOCtl = pDevIOCtl;
159}
160//******************************************************************************
161//******************************************************************************
162HMDeviceDriver::HMDeviceDriver(LPCSTR lpDeviceName)
163 : HMDeviceKernelObjectClass(lpDeviceName)
164{
165}
166//******************************************************************************
167//******************************************************************************
168DWORD HMDeviceDriver::CreateFile (LPCSTR lpFileName,
169 PHMHANDLEDATA pHMHandleData,
170 PVOID lpSecurityAttributes,
171 PHMHANDLEDATA pHMHandleDataTemplate)
172{
173 APIRET rc;
174 HFILE hfFileHandle = 0L; /* Handle for file being manipulated */
175 ULONG ulAction = 0; /* Action taken by DosOpen */
176 ULONG sharetype = 0;
177
178 if(pHMHandleData->dwAccess & (GENERIC_READ | GENERIC_WRITE))
179 sharetype |= OPEN_ACCESS_READWRITE;
180 else
181 if(pHMHandleData->dwAccess & GENERIC_WRITE)
182 sharetype |= OPEN_ACCESS_WRITEONLY;
183
184 if(pHMHandleData->dwShare == 0)
185 sharetype |= OPEN_SHARE_DENYREADWRITE;
186 else
187 if(pHMHandleData->dwShare & (FILE_SHARE_READ | FILE_SHARE_WRITE))
188 sharetype |= OPEN_SHARE_DENYNONE;
189 else
190 if(pHMHandleData->dwShare & FILE_SHARE_WRITE)
191 sharetype |= OPEN_SHARE_DENYREAD;
192 else
193 if(pHMHandleData->dwShare & FILE_SHARE_READ)
194 sharetype |= OPEN_SHARE_DENYWRITE;
195
196 if(szOS2Name[0] == 0) {
197 pHMHandleData->hHMHandle = 0;
198 return (NO_ERROR);
199 }
200
201tryopen:
202 rc = DosOpen( szOS2Name, /* File path name */
203 &hfFileHandle, /* File handle */
204 &ulAction, /* Action taken */
205 0,
206 FILE_NORMAL,
207 FILE_OPEN,
208 sharetype,
209 0L); /* No extended attribute */
210
211 if(rc == ERROR_TOO_MANY_OPEN_FILES) {
212 ULONG CurMaxFH;
213 LONG ReqCount = 32;
214
215 rc = DosSetRelMaxFH(&ReqCount, &CurMaxFH);
216 if(rc) {
217 dprintf(("DosSetRelMaxFH returned %d", rc));
218 return rc;
219 }
220 dprintf(("DosOpen failed -> increased nr open files to %d", CurMaxFH));
221 goto tryopen;
222 }
223
224 dprintf(("DosOpen %s returned %d\n", szOS2Name, rc));
225
226 if(rc == NO_ERROR) {
227 pHMHandleData->hHMHandle = hfFileHandle;
228 return (NO_ERROR);
229 }
230 else return(rc);
231}
232//******************************************************************************
233//******************************************************************************
234BOOL HMDeviceDriver::CloseHandle(PHMHANDLEDATA pHMHandleData)
235{
236 DWORD rc = 0;
237
238 if(pHMHandleData->hHMHandle) {
239 rc = DosClose(pHMHandleData->hHMHandle);
240 }
241 pHMHandleData->hHMHandle = 0;
242 return rc;
243}
244//******************************************************************************
245//******************************************************************************
246BOOL HMDeviceDriver::DeviceIoControl(PHMHANDLEDATA pHMHandleData, DWORD dwIoControlCode,
247 LPVOID lpInBuffer, DWORD nInBufferSize,
248 LPVOID lpOutBuffer, DWORD nOutBufferSize,
249 LPDWORD lpBytesReturned, LPOVERLAPPED lpOverlapped)
250{
251 return devIOCtl(pHMHandleData->hHMHandle, dwIoControlCode, lpInBuffer, nInBufferSize,
252 lpOutBuffer, nOutBufferSize, lpBytesReturned, lpOverlapped);
253}
254//******************************************************************************
255//******************************************************************************
256static BOOL GpdDevIOCtl(HANDLE hDevice, DWORD dwIoControlCode, LPVOID lpInBuffer, DWORD nInBufferSize, LPVOID lpOutBuffer, DWORD nOutBufferSize, LPDWORD lpBytesReturned, LPOVERLAPPED lpOverlapped)
257{
258 ULONG port, val = 0;
259
260 if(fX86Init == FALSE) {
261 if(io_init() == 0)
262 fX86Init = TRUE;
263 else return(FALSE);
264 }
265
266 *lpBytesReturned = 0;
267
268 port = ((GENPORT_WRITE_INPUT *)lpInBuffer)->PortNumber;
269
270 switch((dwIoControlCode >> 2) & 0xFFF) {
271 case IOCTL_GPD_READ_PORT_UCHAR:
272 if(nOutBufferSize < sizeof(char))
273 return(FALSE);
274
275 val = c_inb(port);
276 *(char *)lpOutBuffer = val;
277 *lpBytesReturned = sizeof(char);
278 break;
279 case IOCTL_GPD_READ_PORT_USHORT:
280 if(nOutBufferSize < sizeof(USHORT))
281 return(FALSE);
282
283 val = c_inw(port);
284 *(USHORT *)lpOutBuffer = val;
285 *lpBytesReturned = sizeof(USHORT);
286 break;
287 case IOCTL_GPD_READ_PORT_ULONG:
288 if(nOutBufferSize < sizeof(ULONG))
289 return(FALSE);
290
291 val = c_inl(port);
292 *(ULONG *)lpOutBuffer = val;
293 *lpBytesReturned = sizeof(ULONG);
294 break;
295 case IOCTL_GPD_WRITE_PORT_UCHAR:
296 val = ((GENPORT_WRITE_INPUT *)lpInBuffer)->CharData;
297 c_outb(port, val);
298 break;
299 case IOCTL_GPD_WRITE_PORT_USHORT:
300 val = ((GENPORT_WRITE_INPUT *)lpInBuffer)->ShortData;
301 c_outw(port, val);
302 break;
303 case IOCTL_GPD_WRITE_PORT_ULONG:
304 val = ((GENPORT_WRITE_INPUT *)lpInBuffer)->LongData;
305 c_outl(port, val);
306 break;
307 default:
308 dprintf(("GpdDevIOCtl unknown func %X\n", (dwIoControlCode >> 2) & 0xFFF));
309 return(FALSE);
310 }
311
312 return(TRUE);
313}
314//******************************************************************************
315//******************************************************************************
316static BOOL MAPMEMIOCtl(HANDLE hDevice, DWORD dwIoControlCode, LPVOID lpInBuffer, DWORD nInBufferSize, LPVOID lpOutBuffer, DWORD nOutBufferSize, LPDWORD lpBytesReturned, LPOVERLAPPED lpOverlapped)
317{
318 PHYSICAL_MEMORY_INFO *meminfo = (PHYSICAL_MEMORY_INFO *)lpInBuffer;
319 struct map_ioctl memmap;
320
321 *lpBytesReturned = 0;
322
323 switch((dwIoControlCode >> 2) & 0xFFF) {
324 case IOCTL_MAPMEM_MAP_USER_PHYSICAL_MEMORY:
325 if(nInBufferSize != sizeof(PHYSICAL_MEMORY_INFO))
326 return(FALSE);
327
328 memmap.a.phys = meminfo->BusAddress.u.LowPart;
329 memmap.size = meminfo->Length;
330 dprintf(("DeviceIoControl map phys address %X length %X\n", memmap.a.phys, memmap.size));
331 if(mpioctl((HFILE)hDevice, IOCTL_MAP, &memmap) == -1) {
332 dprintf(("mpioctl failed!\n"));
333 return(FALSE);
334 }
335
336 dprintf(("DeviceIoControl map virt address = %X\n", memmap.a.user));
337 *(ULONG *)lpOutBuffer = (ULONG)memmap.a.user;
338 break;
339 case IOCTL_MAPMEM_UNMAP_USER_PHYSICAL_MEMORY:
340 dprintf(("Unmap mapping %X\n", *(ULONG *)lpInBuffer));
341 memmap.a.phys = *(ULONG *)lpInBuffer;
342 memmap.size = 0;
343 if(mpioctl((HFILE)hDevice, IOCTL_MAP, &memmap) == -1)
344 return(FALSE);
345 break;
346 default:
347 dprintf(("MAPMEMIOCtl unknown func %X\n", (dwIoControlCode >> 2) & 0xFFF));
348 return(FALSE);
349 }
350
351 return(TRUE);
352}
353//******************************************************************************
354//******************************************************************************
355static BOOL FXMEMMAPIOCtl(HANDLE hDevice, DWORD dwIoControlCode, LPVOID lpInBuffer, DWORD nInBufferSize, LPVOID lpOutBuffer, DWORD nOutBufferSize, LPDWORD lpBytesReturned, LPOVERLAPPED lpOverlapped)
356{
357 struct map_ioctl memmap;
358 MAPDEVREQUEST *vxdmem = (MAPDEVREQUEST *)lpInBuffer;
359
360 switch(dwIoControlCode) {
361 case 1:
362 break;
363 case 2:
364 memmap.a.phys = (DWORD)vxdmem->mdr_PhysicalAddress;
365 memmap.size = vxdmem->mdr_SizeInBytes;
366 dprintf(("DeviceIoControl map phys address %X length %X\n", memmap.a.phys, memmap.size));
367 if(mpioctl((HFILE)hDevice, IOCTL_MAP, &memmap) == -1) {
368 dprintf(("mpioctl failed!\n"));
369 return(FALSE);
370 }
371
372 dprintf(("DeviceIoControl map virt address = %X\n", memmap.a.user));
373 vxdmem->mdr_LinearAddress = (PVOID)memmap.a.user;
374 break;
375 default:
376 dprintf(("FXMEMMAPIOCtl unknown func %X\n", (dwIoControlCode >> 2) & 0xFFF));
377 return(FALSE);
378 }
379
380 return(TRUE);
381}
382//******************************************************************************
383//******************************************************************************
384static BOOL VPCIOCtl(HANDLE hDevice, DWORD dwIoControlCode, LPVOID lpInBuffer, DWORD nInBufferSize, LPVOID lpOutBuffer, DWORD nOutBufferSize, LPDWORD lpBytesReturned, LPOVERLAPPED lpOverlapped)
385{
386 APIRET rc;
387
388 dprintf(("VPCIOCtl func %x: %x %d %x %d %x %x", dwIoControlCode, lpInBuffer, nInBufferSize, lpOutBuffer, nOutBufferSize, lpBytesReturned, lpOverlapped));
389 switch(dwIoControlCode) {
390 case 0x9C402880: //0x00
391 if(nOutBufferSize < 4) {
392 SetLastError(ERROR_BAD_LENGTH);
393 return FALSE;
394 }
395 *(DWORD *)lpOutBuffer = 0x60001;
396 *lpBytesReturned = 4;
397 return TRUE;
398
399 case 0x9C402894: //0x14 (get IDT table)
400 {
401 DWORD *lpBuffer = (DWORD *)lpOutBuffer;
402 if(nOutBufferSize < 0x800) {
403 SetLastError(ERROR_BAD_LENGTH);
404 return FALSE;
405 }
406 memset(lpOutBuffer, 0, nOutBufferSize);
407 for(int i=0;i<32;i++) {
408 lpBuffer[i*2] = 0x0178c4c8;
409 lpBuffer[i*2+1] = 0xfff18F00;
410 }
411 for(i=0x50;i<0x57;i++) {
412 lpBuffer[i*2] = 0x0178c4c8;
413 lpBuffer[i*2+1] = 0xfff18E00;
414 }
415 for(i=0x70;i<0x77;i++) {
416 lpBuffer[i*2] = 0x0178c4c8;
417 lpBuffer[i*2+1] = 0xfff18E00;
418 }
419 lpBuffer[0x4F*2] = 0x0178c4c8;
420 lpBuffer[0x4F*2+1] = 0xfff18E00;
421 lpBuffer[0xEF*2] = 0x0178c4c8;
422 lpBuffer[0xEF*2+1] = 0xfff18E00;
423 *lpBytesReturned = 0xFF*8;
424 return TRUE;
425 }
426 case 0x9C40288C: //0x0C change IDT
427 if(nInBufferSize < 0x22) {
428 SetLastError(ERROR_BAD_LENGTH);
429 return FALSE;
430 }
431 fVirtualPC = TRUE;
432 return TRUE;
433
434 case 0x9C402884: //0x04 ExAllocatePoolWithTag
435 {
436 DWORD *lpBuffer = (DWORD *)lpInBuffer;
437 if(nInBufferSize < 0x08) {
438 SetLastError(ERROR_BAD_LENGTH);
439 return FALSE;
440 }
441 dprintf(("In: %x %x", lpBuffer[0], lpBuffer[1]));
442 return TRUE;
443 }
444
445 case 0x9C402898: //0x18 Remove IDT patch
446 if(nInBufferSize < 0x01) {
447 SetLastError(ERROR_BAD_LENGTH);
448 return FALSE;
449 }
450 fVirtualPC = FALSE;
451 return TRUE;
452 default:
453 dprintf(("VPCIOCtl unknown func %X\n", dwIoControlCode));
454 return FALSE;
455 }
456}
457//******************************************************************************
458//******************************************************************************
459HMCustomDriver::HMCustomDriver(HINSTANCE hInstance, LPCSTR lpDeviceName)
460 : HMDeviceDriver(lpDeviceName), hDrvDll(0)
461{
462 hDrvDll = hInstance ;
463 *(ULONG *)&driverOpen = (ULONG)GetProcAddress(hDrvDll, "DrvOpen");
464 *(ULONG *)&driverClose = (ULONG)GetProcAddress(hDrvDll, "DrvClose");
465 *(ULONG *)&driverIOCtl = (ULONG)GetProcAddress(hDrvDll, "DrvIOCtl");
466}
467//******************************************************************************
468//******************************************************************************
469HMCustomDriver::HMCustomDriver(PFNDRVOPEN pfnDriverOpen, PFNDRVCLOSE pfnDriverClose,
470 PFNDRVIOCTL pfnDriverIOCtl, LPCSTR lpDeviceName)
471 : HMDeviceDriver(lpDeviceName), hDrvDll(0)
472{
473 driverOpen = pfnDriverOpen;
474 driverClose = pfnDriverClose;
475 driverIOCtl = pfnDriverIOCtl;
476}
477//******************************************************************************
478//******************************************************************************
479HMCustomDriver::~HMCustomDriver()
480{
481 if(hDrvDll) FreeLibrary(hDrvDll);
482}
483//******************************************************************************
484//******************************************************************************
485DWORD HMCustomDriver::CreateFile (LPCSTR lpFileName,
486 PHMHANDLEDATA pHMHandleData,
487 PVOID lpSecurityAttributes,
488 PHMHANDLEDATA pHMHandleDataTemplate)
489{
490 pHMHandleData->hHMHandle = driverOpen(pHMHandleData->dwAccess, pHMHandleData->dwShare);
491 if(pHMHandleData->hHMHandle == 0) {
492 return 2;
493 }
494 return 0;
495}
496//******************************************************************************
497//******************************************************************************
498BOOL HMCustomDriver::CloseHandle(PHMHANDLEDATA pHMHandleData)
499{
500 if(pHMHandleData->hHMHandle) {
501 driverClose(pHMHandleData->hHMHandle);
502 }
503 pHMHandleData->hHMHandle = 0;
504 return TRUE;
505}
506//******************************************************************************
507//******************************************************************************
508BOOL HMCustomDriver::DeviceIoControl(PHMHANDLEDATA pHMHandleData, DWORD dwIoControlCode,
509 LPVOID lpInBuffer, DWORD nInBufferSize,
510 LPVOID lpOutBuffer, DWORD nOutBufferSize,
511 LPDWORD lpBytesReturned, LPOVERLAPPED lpOverlapped)
512{
513 BOOL ret;
514
515 ret = driverIOCtl(pHMHandleData->hHMHandle, dwIoControlCode, lpInBuffer, nInBufferSize,
516 lpOutBuffer, nOutBufferSize, lpBytesReturned, lpOverlapped);
517 dprintf(("DeviceIoControl %x returned %d", dwIoControlCode, ret));
518 return ret;
519}
520//******************************************************************************
521//******************************************************************************
522BOOL WIN32API QueryPerformanceCounter(LARGE_INTEGER *lpPerformanceCount)
523{
524 QWORD time;
525 APIRET rc;
526
527 rc = DosTmrQueryTime(&time);
528 if(rc) {
529 dprintf(("DosTmrQueryTime returned %d\n", rc));
530 return(FALSE);
531 }
532 lpPerformanceCount->u.LowPart = time.ulLo;
533 lpPerformanceCount->u.HighPart = time.ulHi;
534 return(TRUE);
535}
536//******************************************************************************
537//******************************************************************************
538BOOL WIN32API QueryPerformanceFrequency(LARGE_INTEGER *lpFrequency)
539{
540 APIRET rc;
541 ULONG freq;
542
543 rc = DosTmrQueryFreq(&freq);
544 if(rc) {
545 dprintf(("DosTmrQueryFreq returned %d\n", rc));
546 return(FALSE);
547 }
548 lpFrequency->u.LowPart = freq;
549 lpFrequency->u.HighPart = 0;
550 dprintf2(("QueryPerformanceFrequency returned 0x%X%X\n", lpFrequency->u.HighPart, lpFrequency->u.LowPart));
551 return(TRUE);
552}
553//******************************************************************************
554//******************************************************************************
Note: See TracBrowser for help on using the repository browser.