diff --git a/src/dos/dos_memory.cpp b/src/dos/dos_memory.cpp index 969da3aef..c412dc402 100644 --- a/src/dos/dos_memory.cpp +++ b/src/dos/dos_memory.cpp @@ -28,6 +28,7 @@ // uncomment for alloc/free debug messages #define DEBUG_ALLOC +uint16_t CONV_MAX_SEG = 0xA000; Bitu UMB_START_SEG = 0x9FFF; /* FIXME: This should be a variable that reflects the last RAM segment. * That means 0x9FFF if 640KB or more, or a lesser value if less than 640KB */ @@ -38,7 +39,7 @@ uint16_t first_umb_size = 0x2000; static uint16_t memAllocStrategy = 0x00; -static void DOS_Mem_E_Exit(const char *msg) { +static void DOS_Mem_MCBdump(void) { uint16_t mcb_segment=dos.firstMCB; DOS_MCB mcb(mcb_segment); DOS_MCB mcb_next(0); @@ -65,6 +66,10 @@ static void DOS_Mem_E_Exit(const char *msg) { LOG_MSG("FINAL: Type=0x%02x(%c) Seg=0x%04x size=0x%04x name='%s'\n", mcb.GetType(),c,mcb_segment+1,mcb.GetSize(),name); LOG_MSG("End dump\n"); +} + +static void DOS_Mem_E_Exit(const char *msg) { + DOS_Mem_MCBdump(); #if C_DEBUG LOG_MSG("DOS fatal memory error: %s",msg); @@ -74,7 +79,7 @@ static void DOS_Mem_E_Exit(const char *msg) { #endif } -void DOS_CompressMemory(uint16_t first_segment=0/*default*/) { +void DOS_CompressMemory(uint16_t first_segment=0/*default*/,uint32_t healfrom=0xFFFFu) { uint16_t mcb_segment=dos.firstMCB; DOS_MCB mcb(mcb_segment); DOS_MCB mcb_next(0); @@ -82,8 +87,26 @@ void DOS_CompressMemory(uint16_t first_segment=0/*default*/) { while (mcb.GetType()!='Z') { if(counter++ > 10000000) DOS_Mem_E_Exit("DOS_CompressMemory: DOS MCB list corrupted."); - mcb_next.SetPt((uint16_t)(mcb_segment+mcb.GetSize()+1)); - if (GCC_UNLIKELY((mcb_next.GetType()!=0x4d) && (mcb_next.GetType()!=0x5a))) DOS_Mem_E_Exit("Corrupt MCB chain"); + const uint16_t nseg = (uint16_t)(mcb_segment+mcb.GetSize()+1); + mcb_next.SetPt(nseg); + if (GCC_UNLIKELY((mcb_next.GetType()!=0x4d) && (mcb_next.GetType()!=0x5a))) { + /* there are some programs that chain load other programs, but when they shrink their MCB down + * to their EXE resident size they put the stack or other data in the way of the next MCB free + * block header. Real MS-DOS appears in this case to just scan up to the last valid block and + * then right after it, write a new free MCB block there. We can't do this for the split memory + * layout of the PCjr emulation. */ + if (nseg >= healfrom && (nseg+1u) < CONV_MAX_SEG && !(machine==MCH_PCJR)) { + LOG(LOG_DOSMISC,LOG_ERROR)("Corrupted MCB chain, but within the possible memory region of the DOS application."); + DOS_Mem_MCBdump(); + LOG(LOG_DOSMISC,LOG_ERROR)("Declaring all memory past it as a free block. This is apparently MS-DOS behavior."); + mcb_next.SetSize(CONV_MAX_SEG - (nseg + 1u)); + mcb_next.SetPSPSeg(MCB_FREE); + mcb_next.SetType('Z'); + } + else { + DOS_Mem_E_Exit("Corrupt MCB chain"); + } + } if (mcb_segment >= first_segment && (mcb.GetPSPSeg()==MCB_FREE) && (mcb_next.GetPSPSeg()==MCB_FREE)) { mcb.SetSize(mcb.GetSize()+mcb_next.GetSize()+1); mcb.SetType(mcb_next.GetType()); @@ -184,7 +207,12 @@ uint16_t DOS_GetMaximumFreeSize(uint16_t minBlocks) } bool DOS_AllocateMemory(uint16_t * segment,uint16_t * blocks) { - DOS_CompressMemory(); + DOS_MCB psp_mcb(dos.psp()-1); + + DOS_CompressMemory( + 0/*default scan*/, + dos.psp()+psp_mcb.GetSize()/*auto-heal broken MCB chain if it occurs anywhere past the PSP memory block*/); + uint16_t bigsize=0; uint16_t mem_strat=memAllocStrategy; uint16_t mcb_segment=dos.firstMCB; @@ -197,7 +225,6 @@ bool DOS_AllocateMemory(uint16_t * segment,uint16_t * blocks) { DOS_MCB mcb(0); DOS_MCB mcb_next(0); - DOS_MCB psp_mcb(dos.psp()-1); char psp_name[9]; psp_mcb.GetFileName(psp_name); if (umb_start==UMB_START_SEG && (dos.loaded_codepage == 936 || dos.loaded_codepage == 950 || dos.loaded_codepage == 951)) { @@ -640,6 +667,7 @@ void DOS_SetupMemory(void) { /* map memory as normal, the BIOS initialization is the code responsible * for subtracting 32KB from top of system memory for video memory. */ mcb.SetSize(/*normally 0x97FF*/(seg_limit-1) - DOS_MEM_START - mcb_sizes); + CONV_MAX_SEG = seg_limit; } else if (machine==MCH_PCJR) { /* If there is more than 128KB of RAM, then the MCB chain must be constructed * to exclude video memory. In that case, the BIOS values in the BDA will report @@ -656,6 +684,7 @@ void DOS_SetupMemory(void) { mcb_devicedummy.SetPSPSeg(MCB_FREE); mcb_devicedummy.SetSize(/*0x9FFF*/(seg_limit-1) - 0x2000); mcb_devicedummy.SetType(0x5a); + CONV_MAX_SEG = seg_limit; /* exclude PCJr graphics region */ mcb_devicedummy.SetPt((uint16_t)0x17ff); @@ -670,6 +699,7 @@ void DOS_SetupMemory(void) { else { /* Normal MCB chain, nothing special */ mcb.SetSize(/*normally 0x97FF*/(seg_limit-1) - DOS_MEM_START - mcb_sizes); + CONV_MAX_SEG = seg_limit; } } else { #ifndef DEBUG_ALLOC @@ -684,6 +714,7 @@ void DOS_SetupMemory(void) { /* complete memory up to 640k available */ /* last paragraph used to add UMB chain to low-memory MCB chain */ mcb.SetSize(/*0x9FFE*/(seg_limit-2) - DOS_MEM_START - mcb_sizes); + CONV_MAX_SEG = seg_limit-1; } dos.firstMCB=DOS_MEM_START;