/*++ Copyright (c) 1997 Microsoft Corporation Module Name: net.c Abstract: This module implements the net boot file system used by the operating system loader. It only contains those functions which are firmware/BIOS dependent. Author: Chuck Lenzmeier (chuckl) 09-Jan-1997 Revision History: --*/ #include "bootlib.h" #include "stdio.h" #ifdef UINT16 #undef UINT16 #endif #ifdef INT16 #undef INT16 #endif #include #include #include #include #include #include #include "bootx86.h" #ifndef BOOL typedef int BOOL; #endif #ifndef FALSE #define FALSE 0 #endif #ifndef TRUE #define TRUE 1 #endif #ifndef BYTE typedef unsigned char BYTE; #endif #ifndef LPBYTE typedef BYTE *LPBYTE; #endif #define MAX_PATH 260 // // Define global data. // CHAR NetBootPath[129]; ULONG NetLocalIpAddress; ULONG NetLocalSubnetMask; ULONG NetServerIpAddress; ULONG NetGatewayIpAddress; UCHAR NetLocalHardwareAddress[16]; UCHAR NetBootIniContents[1020 + 1]; // 4 * 255 = 1020 + 1 UCHAR NetBootIniPath[256 + 1]; USHORT NetMaxTranUnit = 0; // MTU USHORT NetHwAddrLen = 0; // actual length of hardware address USHORT NetHwType = 0; // Type of protocol at the hardware level from rfc1010 UCHAR MyGuid[16]; ULONG MyGuidLength = sizeof(MyGuid); BOOLEAN MyGuidValid = FALSE; ARC_STATUS FindDhcpOption( IN BOOTPLAYER * Packet, IN UCHAR Option, IN ULONG MaximumLength, OUT PUCHAR OptionData, OUT PULONG Length OPTIONAL, IN ULONG Instance OPTIONAL ) /*++ Routine Description: Searches a dhcp packet for a given option. Arguments: Packet - pointer to the dhcp packet. Caller is responsible for assuring that the packet is a valid dhcp packet. Option - the dhcp option we're searching for. MaximumLength - size in bytes of OptionData buffer. OptionData - buffer to receive the option. Length - if specified, receives the actual length of option copied. Instance - specifies which instance of the option you are searching for. If not specified (zero), then we just grab the first instance of the tag. Return Value: None. --*/ { PUCHAR curOption; ULONG copyLength; ULONG i = 0; if (MaximumLength == 0) { return EINVAL; } RtlZeroMemory(OptionData, MaximumLength); // // Parse the DHCP options looking for a specific one. // curOption = &Packet->vendor.d[4]; // skip the magic cookie while ((curOption - (PUCHAR)Packet) < sizeof(BOOTPLAYER) && *curOption != 0xff) { if (*curOption == DHCP_PAD) { // // just walk past any pad options // these will not have any length // curOption++; } else { if (*curOption == Option) { // // Found it, copy and leave. // if ( i == Instance ) { if (sizeof(BOOTPLAYER) <= curOption + 2 - (PUCHAR)Packet || sizeof(BOOTPLAYER) <= curOption + 2 + curOption[1] - (PUCHAR)Packet ) { // // invalid option. it walked past the end of the packet // break; } if (curOption[1] > MaximumLength) { copyLength = MaximumLength; } else { copyLength = curOption[1]; } RtlCopyMemory(OptionData, curOption+2, copyLength); if (ARGUMENT_PRESENT(Length)) { *Length = copyLength; } return ESUCCESS; } i++; } curOption = curOption + 2 + curOption[1]; } } return EINVAL; } ARC_STATUS GetParametersFromRom ( VOID ) { SHORT status; t_PXENV_GET_BINL_INFO gbi; t_PXENV_UNDI_GET_INFORMATION info; BOOTPLAYER packet; ULONG temp; ULONG i; PCHAR p; NetLocalIpAddress = 0; NetGatewayIpAddress = 0; NetServerIpAddress = 0; NetLocalSubnetMask = 0; *NetBootPath = 0; RtlZeroMemory( NetBootIniContents, sizeof(NetBootIniContents) ) ; RtlZeroMemory( NetBootIniPath, sizeof(NetBootIniPath) ) ; // // Get client IP address, server IP address, default gateway IP address, // and subnet mask from the DHCP ACK packet. // RtlZeroMemory( &packet, sizeof(packet) ) ; gbi.packet_type = PXENV_PACKET_TYPE_DHCP_ACK; gbi.buffer_size = sizeof(packet); gbi.buffer_offset = (USHORT)((ULONG_PTR)&packet & 0x0f); gbi.buffer_segment = (USHORT)(((ULONG_PTR)&packet >> 4) & 0xffff); status = NETPC_ROM_SERVICES( PXENV_GET_BINL_INFO, &gbi ); if ( status != PXENV_EXIT_SUCCESS ) { DPRINT( ERROR, ("PXENV_GET_BINL_INFO(1) failed with %x\n", status) ); } else { NetLocalIpAddress = *(ULONG *)packet.yip; NetServerIpAddress = *(ULONG *)packet.sip; if (FindDhcpOption(&packet, DHCP_ROUTER, sizeof(temp), (PUCHAR)&temp, NULL, 0) == ESUCCESS) { NetGatewayIpAddress = temp; } else { NetGatewayIpAddress = *(ULONG *)packet.gip; } memcpy(NetLocalHardwareAddress, packet.caddr, 16); if (FindDhcpOption(&packet, DHCP_SUBNET, sizeof(temp), (PUCHAR)&temp, NULL, 0) == ESUCCESS) { NetLocalSubnetMask = temp; } } // // Values for client IP address, server IP address, default gateway IP address, // and subnet mask that are present in the BINL REPLY packet override those // in the DHCP ACK packet. // RtlZeroMemory( &packet, sizeof(packet) ) ; gbi.packet_type = PXENV_PACKET_TYPE_BINL_REPLY; gbi.buffer_size = sizeof(packet); gbi.buffer_offset = (USHORT)((ULONG_PTR)&packet & 0x0f); gbi.buffer_segment = (USHORT)(((ULONG_PTR)&packet >> 4) & 0xffff); status = NETPC_ROM_SERVICES( PXENV_GET_BINL_INFO, &gbi ); if ( status != PXENV_EXIT_SUCCESS ) { DPRINT( ERROR, ("PXENV_GET_BINL_INFO(2) failed with %x\n", status) ); return ENODEV; } if ( *(ULONG *)packet.yip != 0 ) { NetLocalIpAddress = *(ULONG *)packet.yip; } if ( *(ULONG *)packet.sip != 0 ) { NetServerIpAddress = *(ULONG *)packet.sip; } if (FindDhcpOption(&packet, DHCP_ROUTER, sizeof(temp), (PUCHAR)&temp, NULL, 0) == ESUCCESS) { NetGatewayIpAddress = temp; } else if ( *(ULONG *)packet.gip != 0 ) { NetGatewayIpAddress = *(ULONG *)packet.gip; } if (FindDhcpOption(&packet, DHCP_SUBNET, sizeof(temp), (PUCHAR)&temp, NULL, 0) == ESUCCESS) { NetLocalSubnetMask = temp; } DPRINT( ERROR, ("Client: %x, Subnet mask: %x; Server: %x; Gateway: %x\n", NetLocalIpAddress, NetLocalSubnetMask, NetServerIpAddress, NetGatewayIpAddress) ); // // Find the path of the boot filename (the part before the actual name). // // // do the strncpy first. that way we know the string is null // terminated, and we are then allowed to use standard str // routines (like the strrchr below) // strncpy( NetBootPath, (PCHAR)packet.bootfile, sizeof(NetBootPath) ); NetBootPath[sizeof(NetBootPath)-1] = '\0'; p = strrchr(NetBootPath,'\\'); if (p) { p += 1; // advance it past the '\' *p = '\0'; // terminate the path } else { NetBootPath[0] = '\0'; } // // The BINL server could optionally specify two private DHCP option tags // that are used for processing boot.ini. // // DHCP_LOADER_BOOT_INI would contain the entire contents of boot.ini // and is limited to 1024 bytes. Note that each DHCP option tags is // to 255 bytes. Boot.ini contents can be broken into multiple instances // of the same tag. We support up to 4 instances = 1020 bytes. // for (i = 0; i < 4; i++) { if (FindDhcpOption( &packet, DHCP_LOADER_BOOT_INI, 255, &NetBootIniContents[i * 255], NULL, i) != ESUCCESS ) { break; } } // // DHCP_LOADER_BOOT_INI_PATH contains a path to a boot.ini file and is // ignored if DHCP_LOADER_BOOT_INI has been specified. // FindDhcpOption(&packet, DHCP_LOADER_BOOT_INI_PATH, sizeof(NetBootIniPath), NetBootIniPath, NULL, 0); // // Get UNDI information // RtlZeroMemory(&info, sizeof(info)); status = NETPC_ROM_SERVICES( PXENV_UNDI_GET_INFORMATION, &info ); if ((status != PXENV_EXIT_SUCCESS) || (info.Status != PXENV_EXIT_SUCCESS)) { DPRINT( ERROR, ("PXENV_UNDI_GET_INFORMATION failed with %x, status = %x\n", status, info.Status) ); return ENODEV; } NetMaxTranUnit = info.MaxTranUnit; NetHwAddrLen = info.HwAddrLen; NetHwType = info.HwType; memcpy( NetLocalHardwareAddress, info.PermNodeAddress, ADDR_LEN ); return ESUCCESS; } ARC_STATUS GetGuid( OUT PUCHAR *Guid, OUT PULONG GuidLength ) /*++ Routine Description: This routine returns the Guid of this machine. Arguments: Guid - Place to store pointer to the guid. GuidLength - Place to store the length in bytes of the guid. Return Value: ARC code indicating outcome. --*/ { t_PXENV_GET_BINL_INFO gbi; BOOTPLAYER packet; SHORT romStatus; ARC_STATUS Status; UCHAR TmpBuffer[sizeof(MyGuid) + 1]; if (!MyGuidValid) { RtlZeroMemory( &packet, sizeof(packet) ) ; gbi.packet_type = PXENV_PACKET_TYPE_BINL_REPLY; gbi.buffer_size = sizeof(packet); gbi.buffer_offset = (USHORT)((ULONG_PTR)&packet & 0x0f); gbi.buffer_segment = (USHORT)(((ULONG_PTR)&packet >> 4) & 0xffff); romStatus = NETPC_ROM_SERVICES( PXENV_GET_BINL_INFO, &gbi ); if ( romStatus == PXENV_EXIT_SUCCESS ) { Status = FindDhcpOption(&packet, DHCP_CLIENT_GUID, sizeof(TmpBuffer), TmpBuffer, &MyGuidLength, 0); if (Status == ESUCCESS) { if (MyGuidLength > sizeof(MyGuid)) { // // use the end of the GUID if it's too large. // memcpy(MyGuid, TmpBuffer + (MyGuidLength - sizeof(MyGuid)), sizeof(MyGuid)); MyGuidLength = sizeof(MyGuid); } else { memcpy(MyGuid, TmpBuffer, MyGuidLength); } *Guid = MyGuid; *GuidLength = MyGuidLength; MyGuidValid = TRUE; return ESUCCESS; } } // // Use the NIC hardware address as a GUID // memset(MyGuid, 0x0, sizeof(MyGuid)); memcpy(MyGuid + sizeof(MyGuid) - sizeof(NetLocalHardwareAddress), NetLocalHardwareAddress, sizeof(NetLocalHardwareAddress) ); MyGuidLength = sizeof(MyGuid); MyGuidValid = TRUE; } *Guid = MyGuid; *GuidLength = MyGuidLength; return ESUCCESS; } ULONG CalculateChecksum( IN PLONG Block, IN ULONG Length ) /*++ Routine Description: This routine calculates a simple two's-complement checksum of a block of memory. If the returned value is stored in the block (in a word that was zero during the calculation), then new checksum of the block will be zero. Arguments: Block - Address of a block of data. Must be 4-byte aligned. Length - Length of the block. Must be a multiple of 4. Return Value: ULONG - Two's complement additive checksum of the input block. --*/ { LONG checksum = 0; ASSERT( ((ULONG_PTR)Block & 3) == 0 ); ASSERT( (Length & 3) == 0 ); for ( ; Length != 0; Length -= 4 ) { checksum += *Block; Block++; } return -checksum; } NTSTATUS NetSoftReboot( IN PUCHAR NextBootFile, IN ULONGLONG Param, IN PUCHAR RebootFile OPTIONAL, IN PUCHAR SifFile OPTIONAL, IN PUCHAR User OPTIONAL, IN PUCHAR Domain OPTIONAL, IN PUCHAR Password OPTIONAL, IN PUCHAR AdministratorPassword OPTIONAL ) /*++ Routine Description: This routine does a soft reboot by inserting a fake BINL packet into the ROM and then inserting the filename of the start of a TFTP command. Arguments: NextBootFile - Fully qualified path name of the file to download. Param - Reboot parameter to set. RebootFile - String identifying the file to reboot to when after the current reboot is done. SifFile - Optional SIF file to pass to the next loader. User/Domain/Password/AdministratorPassword - Optional credentials to pass to the next loader. Return Value: Should not return if successful. --*/ { SHORT romStatus; NTSTATUS status = STATUS_SUCCESS; union { t_PXENV_UDP_CLOSE UdpClose; t_PXENV_TFTP_READ_FILE TftpReadFile; } command; t_PXENV_GET_BINL_INFO gbi; BOOTPLAYER * packet; PTFTP_RESTART_BLOCK restartBlock; PTFTP_RESTART_BLOCK_V1 restartBlockV1; DPRINT( TRACE, ("NetSoftReboot ( )\n") ); ASSERT(NextBootFile != NULL); // // Store the reboot parameters in memory. // restartBlock = (PTFTP_RESTART_BLOCK)(0x7C00 + 0x8000 - sizeof(TFTP_RESTART_BLOCK)); RtlZeroMemory(restartBlock, sizeof(TFTP_RESTART_BLOCK)); BlSetHeadlessRestartBlock(restartBlock); if (AdministratorPassword) { RtlMoveMemory(restartBlock->AdministratorPassword,AdministratorPassword, OSC_ADMIN_PASSWORD_LEN); } restartBlockV1 = (PTFTP_RESTART_BLOCK_V1)(0x7C00 + 0x8000 - sizeof(TFTP_RESTART_BLOCK_V1)); restartBlockV1->RebootParameter = Param; if (RebootFile != NULL) { strncpy(restartBlockV1->RebootFile, (PCHAR)RebootFile, sizeof(restartBlockV1->RebootFile)); restartBlockV1->RebootFile[sizeof(restartBlockV1->RebootFile) - 1] = '\0'; } if (SifFile != NULL) { strncpy(restartBlockV1->SifFile, (PCHAR)SifFile, sizeof(restartBlockV1->SifFile)); restartBlockV1->SifFile[sizeof(restartBlockV1->SifFile) - 1] = '\0'; } if (User != NULL) { strncpy(restartBlockV1->User, (PCHAR)User, sizeof(restartBlockV1->User)); restartBlockV1->User[sizeof(restartBlockV1->User) - 1] = '\0'; } if (Domain != NULL) { strncpy(restartBlockV1->Domain, (PCHAR)Domain, sizeof(restartBlockV1->Domain)); restartBlockV1->Domain[sizeof(restartBlockV1->Domain) - 1] = '\0'; } if (Password != NULL) { strncpy(restartBlockV1->Password, (PCHAR)Password, sizeof(restartBlockV1->Password)); restartBlockV1->Password[sizeof(restartBlockV1->Password) - 1] = '\0'; } // // Set the tag in the restart block and calculate and store the checksum. // restartBlockV1->Tag = 'rtsR'; restartBlockV1->Checksum = CalculateChecksum((PLONG)(0x7C00 + 0x8000 - 128), 128); // // For all versions of RIS after NT5.0 we have a new datastructure which is // more adaptable for the future. For this section we have a different checksum, // do that now. // restartBlock->TftpRestartBlockVersion = TFTP_RESTART_BLOCK_VERSION; restartBlock->NewCheckSumLength = FIELD_OFFSET(TFTP_RESTART_BLOCK, RestartBlockV1); restartBlock->NewCheckSum = CalculateChecksum((PLONG)restartBlock, restartBlock->NewCheckSumLength); // // Modify the BINL reply that the ROM has stored so that // the file name looks like the one we are rebooting to // (this is so we can retrieve the path correctly after // reboot, so we know where to look for bootloader). // gbi.packet_type = PXENV_PACKET_TYPE_BINL_REPLY; gbi.buffer_size = 0; gbi.buffer_offset = 0; gbi.buffer_segment = 0; romStatus = NETPC_ROM_SERVICES( PXENV_GET_BINL_INFO, &gbi ); if ( romStatus != PXENV_EXIT_SUCCESS ) { DPRINT( ERROR, ("PXENV_GET_BINL_INFO(1) failed with %x\n", romStatus) ); return STATUS_UNSUCCESSFUL; } // // Now convert the segment/offset to a pointer and modify the // filename. // packet = (BOOTPLAYER *)UIntToPtr( ((gbi.buffer_segment << 4) + gbi.buffer_offset) ); RtlZeroMemory(packet->bootfile, sizeof(packet->bootfile)); strncpy((PCHAR)packet->bootfile, (PCHAR)NextBootFile, sizeof(packet->bootfile)); packet->bootfile[sizeof(packet->bootfile)-1] = '\0'; // // First tell the ROM to shut down its UDP layer. // RtlZeroMemory( &command, sizeof(command) ); command.UdpClose.Status = 0; status = NETPC_ROM_SERVICES( PXENV_UDP_CLOSE, &command ); if ( status != 0 ) { DPRINT( ERROR, ("NetSoftReboot: error %d from UDP_CLOSE\n", status) ); } // // Now tell the ROM to reboot and do a TFTP read of the specified // file from the specifed server. // RtlZeroMemory( &command, sizeof(command) ); command.TftpReadFile.BufferOffset = 0x7c00; // standard boot image location // 32K (max size allowed) less area for passing parameters command.TftpReadFile.BufferSize = 0x8000 - sizeof(TFTP_RESTART_BLOCK); *(ULONG *)command.TftpReadFile.ServerIPAddress = NetServerIpAddress; // // Determine whether we need to send via the gateway. // if ( (NetServerIpAddress & NetLocalSubnetMask) == (NetLocalIpAddress & NetLocalSubnetMask) ) { *(UINT32 *)command.TftpReadFile.GatewayIPAddress = 0; } else { *(UINT32 *)command.TftpReadFile.GatewayIPAddress = NetGatewayIpAddress; } strcpy((PCHAR)command.TftpReadFile.FileName, (PCHAR)NextBootFile); // // This should not return if it succeeds! // romStatus = NETPC_ROM_SERVICES( PXENV_RESTART_TFTP, &command ); if ( (romStatus != 0) || (command.TftpReadFile.Status != 0) ) { DPRINT( ERROR, ("NetSoftReboot: Could not reboot to <%s>, %d/%d\n", NextBootFile, romStatus, command.TftpReadFile.Status) ); status = STATUS_UNSUCCESSFUL; } return status; } VOID NetGetRebootParameters( OUT PULONGLONG Param OPTIONAL, OUT PUCHAR RebootFile OPTIONAL, OUT PUCHAR SifFile OPTIONAL, OUT PUCHAR User OPTIONAL, OUT PUCHAR Domain OPTIONAL, OUT PUCHAR Password OPTIONAL, OUT PUCHAR AdministratorPassword OPTIONAL, BOOLEAN ClearRestartBlock ) /*++ Routine Description: This routine reads the reboot parameters from the TFTP_RESTART_BLOCK that ends at physical address 0x7c00 + 0x8000 and returns them. (then clearing the address) 0x7c00 is the base address for startrom.com 0x8000 is the largest startrom.com allowed. then we reserve some space at the end for parameters. Arguments: Param - Space for returning the value. RebootFile - Optional space for storing the file to reboot to when done here. (size >= char[128]) SifFile - Optional space for storing a SIF file passed from whoever initiated the soft reboot. User/Domain/Password/AdministratorPassword - Optional space to store credentials passed across the soft reboot. ClearRestartBlock - If set to TRUE, it wipes out the memory here - should be done exactly once, at the last call to this function. Return Value: None. --*/ { PTFTP_RESTART_BLOCK restartBlock; PTFTP_RESTART_BLOCK_V1 restartBlockV1; TFTP_RESTART_BLOCK nullRestartBlock; BOOLEAN restartBlockValid; restartBlock = (PTFTP_RESTART_BLOCK)(0x7C00 + 0x8000 - sizeof(TFTP_RESTART_BLOCK)); restartBlockV1 = (PTFTP_RESTART_BLOCK_V1)(0x7C00 + 0x8000 - sizeof(TFTP_RESTART_BLOCK_V1)); // // See if the block is valid. If it's not, we create a temporary empty // one so the copy logic below doesn't have to keep checking. // if ((restartBlockV1->Tag == 'rtsR') && (CalculateChecksum((PLONG)(0x7C00 + 0x8000 - 128), 128) == 0)) { restartBlockValid = TRUE; } else { restartBlockValid = FALSE; RtlZeroMemory( &nullRestartBlock, sizeof(TFTP_RESTART_BLOCK) ); restartBlock = &nullRestartBlock; } // // Copy out the parameters that were in the original TFTP_RESTART_BLOCK structure. // These shipped in Win2K. // // Unfortunetly we do not know the size of the parameters passed to us. // Assume they are no smaller than the fields in the restart block // if (Param != NULL) { *Param = restartBlockV1->RebootParameter; } if (RebootFile != NULL) { memcpy(RebootFile, restartBlockV1->RebootFile, sizeof(restartBlockV1->RebootFile)); } if (SifFile != NULL) { memcpy(SifFile, restartBlockV1->SifFile, sizeof(restartBlockV1->SifFile)); } if (User != NULL) { strncpy((PCHAR)User, restartBlockV1->User, sizeof(restartBlockV1->User)); User[sizeof(restartBlockV1->User)-1] = '\0'; } if (Domain != NULL) { strncpy((PCHAR)Domain, restartBlockV1->Domain, sizeof(restartBlockV1->Domain)); Domain[sizeof(restartBlockV1->Domain)-1] = '\0'; } if (Password != NULL) { strncpy((PCHAR)Password, restartBlockV1->Password, sizeof(restartBlockV1->Password)); Password[sizeof(restartBlockV1->Password)-1] = '\0'; } // // Now do a new check for all versions past Win2K // if (restartBlockValid) { ULONG RestartBlockChecksumPointer = 0; // // Figure out how much of the restart block needs to be checksumed. // RestartBlockChecksumPointer = (ULONG)restartBlockV1; RestartBlockChecksumPointer -= (restartBlock->NewCheckSumLength); RestartBlockChecksumPointer -= (sizeof(restartBlock->NewCheckSumLength)); if ((restartBlock->NewCheckSumLength == 0) || (CalculateChecksum((PLONG)(RestartBlockChecksumPointer), restartBlock->NewCheckSumLength) != 0)) { // // A pre-Win2K OsChooser has given us this block. Clear out all fields // that are post-Win2K and continue. // RtlZeroMemory(restartBlock, FIELD_OFFSET(TFTP_RESTART_BLOCK, RestartBlockV1)); } } // // Now extract the parameters from the block. // if (restartBlock->TftpRestartBlockVersion == TFTP_RESTART_BLOCK_VERSION) { BlGetHeadlessRestartBlock(restartBlock, restartBlockValid); if (AdministratorPassword) { RtlMoveMemory(AdministratorPassword,restartBlock->AdministratorPassword, OSC_ADMIN_PASSWORD_LEN); } } if (restartBlockValid && ClearRestartBlock) { RtlZeroMemory(restartBlock, sizeof(TFTP_RESTART_BLOCK)); } return; } ARC_STATUS NetFillNetworkLoaderBlock ( PNETWORK_LOADER_BLOCK NetworkLoaderBlock ) { SHORT status; t_PXENV_GET_BINL_INFO gbi; BOOTPLAYER packet; // // Get client IP address, server IP address, default gateway IP address, // and subnet mask from the DHCP ACK packet. // gbi.packet_type = PXENV_PACKET_TYPE_DHCP_ACK; gbi.buffer_size = sizeof(packet); gbi.buffer_offset = (USHORT)((ULONG_PTR)&packet & 0x0f); gbi.buffer_segment = (USHORT)(((ULONG_PTR)&packet >> 4) & 0xffff); status = NETPC_ROM_SERVICES( PXENV_GET_BINL_INFO, &gbi ); if ( status != PXENV_EXIT_SUCCESS ) { DbgPrint("PXENV_GET_BINL_INFO(DHCPACK) failed with %x\n", status); return ENODEV; } NetworkLoaderBlock->DHCPServerACK = BlAllocateHeap(gbi.buffer_size); if (NetworkLoaderBlock->DHCPServerACK == NULL) { return ENOMEM; } memcpy( NetworkLoaderBlock->DHCPServerACK, &packet, gbi.buffer_size ); NetworkLoaderBlock->DHCPServerACKLength = gbi.buffer_size; gbi.packet_type = PXENV_PACKET_TYPE_BINL_REPLY; gbi.buffer_size = sizeof(packet); gbi.buffer_offset = (USHORT)((ULONG_PTR)&packet & 0x0f); gbi.buffer_segment = (USHORT)(((ULONG_PTR)&packet >> 4) & 0xffff); status = NETPC_ROM_SERVICES( PXENV_GET_BINL_INFO, &gbi ); if ( status != PXENV_EXIT_SUCCESS ) { DbgPrint("PXENV_GET_BINL_INFO(BINLREPLY) failed with %x\n", status); } else { NetworkLoaderBlock->BootServerReplyPacket = BlAllocateHeap(gbi.buffer_size); if (NetworkLoaderBlock->BootServerReplyPacket == NULL) { return ENOMEM; } memcpy( NetworkLoaderBlock->BootServerReplyPacket, &packet, gbi.buffer_size ); NetworkLoaderBlock->BootServerReplyPacketLength = gbi.buffer_size; } return ESUCCESS; }