// clang++ -c -std=c++11 -stdlib=libc++ -Wno-deprecated-declarations loader.cpp #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include struct { uint32_t ram; uint32_t stack; uint32_t machine; bool traceCPU; bool traceMacsbug; bool traceGlobals; } Flags = { 16 * 1024 * 1024, 8 * 1024, 68030, false, false, false }; uint8_t *Memory; uint32_t HighWater = 0x10000; uint32_t MemorySize = 0; uint32_t EmulatedNewPtr(uint32_t size) { if (size & 0x01) size++; if (HighWater + size > MemorySize) { fprintf(stderr, "Insufficient Memory!\n"); exit(EX_CONFIG); } uint32_t address = HighWater; HighWater += size; std::memset(Memory + HighWater, 0, size); return address; } uint8_t ReadByte(const void *data, uint32_t offset) { offset &= 0xffffff; if (offset >= MemorySize) return 0; return ((uint8_t *)data)[offset]; } uint16_t ReadWord(const void *data, uint32_t offset) { offset &= 0xffffff; return (ReadByte(data, offset) << 8) | ReadByte(data, offset+1); } uint32_t ReadLong(const void *data, uint32_t offset) { offset &= 0xffffff; return (ReadWord(data, offset) << 16) | ReadWord(data, offset+2); } void WriteByte(void *data, uint32_t offset, uint8_t value) { offset &= 0xffffff; if (offset >= MemorySize) return; ((uint8_t *)data)[offset] = value; } void WriteWord(void *data, uint32_t offset, uint16_t value) { offset &= 0xffffff; if (offset + 1 >= MemorySize) return; ((uint8_t *)data)[offset++] = value >> 8; ((uint8_t *)data)[offset++] = value; } void WriteLong(void *data, uint32_t offset, uint32_t value) { offset &= 0xffffff; if (offset + 3 >= MemorySize) return; ((uint8_t *)data)[offset++] = value >> 24; ((uint8_t *)data)[offset++] = value >> 16; ((uint8_t *)data)[offset++] = value >> 8; ((uint8_t *)data)[offset++] = value; } /* //0x0316 = MPWArgs * struct MPWArgs { uint32_t MPGM; // = 'MPGM'; int32_t argptr; args: uint16_t SH; uint32_t argc; uint32_t argv; uint32_t envp; envp stored as key\0value\0, not key=value\0 }; 0x910 = name ptr (32-char pstring) */ uint32_t load(const char *file) { ResFileRefNum refNum; FSRef ref; uint32_t returnAddress = 0; std::vector< std::pair > segments; // segment, address uint32_t a5 = 0; uint32_t jtStart = 0; uint32_t jtEnd = 0; assert(FSPathMakeRef( (const UInt8 *)file, &ref, NULL) == noErr); refNum = FSOpenResFile(&ref, fsRdPerm); assert(refNum != -1 ); int l = Count1Resources('CODE'); segments.reserve(l); assert(l > 0); for (int i = 0; i < l; ++i) { ResAttributes attr; ResID resID; ResType resType; Str255 name; uint32_t size; uint32_t address; Handle h; const uint8_t *data; h = Get1IndResource('CODE', i + 1); if (!h) continue; HLock(h); data = *(const uint8_t **)h; attr = GetResAttrs(h); GetResInfo(h, &resID, &resType, name); size = GetHandleSize(h); if (segments.size() <= resID) segments.resize(resID + 1); // can't have duplicate resIDs, so no need to check that... if (resID == 0) { // jump table/a5 uint32_t above = ReadLong(data, 0); uint32_t below = ReadLong(data, 4); uint32_t jtSize = ReadLong(data, 8); uint32_t jtOffset = ReadLong(data, 12); uint32_t a5size = above + below; address = EmulatedNewPtr(a5size); a5 = address + below; std::memcpy(Memory + a5 + jtOffset, data + 16 , jtSize); segments[resID] = std::make_pair(address, a5size); jtStart = a5 + jtOffset; jtEnd = jtStart + jtSize; // 0x0934 - CurJTOffset (16-bit) WriteWord(Memory, 0x0934, jtOffset); // 0x0904 -- CurrentA5 (32-bit) WriteLong(Memory, 0x0904, a5); cpuSetAReg(5, a5); } else { address = EmulatedNewPtr(size); std::memcpy(Memory + address, data, size); segments[resID] = std::make_pair(address, size); } ReleaseResource(h); } // now link the segment 0 jump table... assert(a5); bool first = true; for (; jtStart < jtEnd; jtStart += 8) { uint16_t offset = ReadWord(Memory, jtStart); uint16_t seg = ReadWord(Memory, jtStart + 4); assert(ReadWord(Memory, jtStart + 2) == 0x3F3C); assert(ReadWord(Memory, jtStart + 6) == 0xA9F0); assert(seg < segments.size()); auto p = segments[seg]; assert(p.first); // missing segment?! assert(offset < p.second); // +4 for the jump table info header. uint32_t address = p.first + offset + 4; // JMP absolute long WriteWord(Memory, jtStart + 2, 0x4EF9); WriteLong(Memory, jtStart + 4, address); if (first) { //cpuSetPC(address); returnAddress = address; first = false; } } // set pc to jump table entry 0. return returnAddress; } void InitializeMPW(int argc, char **argv) { argv[0] = basename(argv[0]); // 0x0910 CurApName { char * name = argv[0]; char str32[32]; int l = strlen(name); l = std::min(l, 32); str32[0] = l; memcpy(str32 + 1, name, l); while (l < 32) str32[l++] = 0; memcpy(Memory + 0x910, str32, 32); } // 0x0316 is a pointer to the argc, argv, envp. // very ugly. uint32_t argvAddress = 0; uint32_t envpAddress = 0; { std::vector argvSpine; argvSpine.reserve(argc + 1); for (int i = 0; i < argc; ++i) { int length; uint32_t address; length = strlen(argv[i]); address = EmulatedNewPtr(length + 1); memcpy(Memory + address, argv[i], length + 1); argvSpine.push_back(address); } argvSpine.push_back(0); argvAddress = EmulatedNewPtr(4 * argvSpine.size()); uint32_t address = argvAddress; for (uint32_t x : argvSpine) { WriteLong(Memory, address, x); address += 4; } } // same thing for envp... but \0 instead of = { envpAddress = EmulatedNewPtr(4); WriteLong(Memory, envpAddress, 0); } // _macpgminfo uint32_t address = EmulatedNewPtr(8 + 0x30); address = 0x2000; // monitor reads... WriteLong(Memory, 0x0316, address); WriteLong(Memory, address, 0x4d50474d); // MPGM - magic WriteLong(Memory, address + 4, 0x2100 /* address + 8 */); // block ptr // address += 8; address = 0x2100; WriteWord(Memory, address + 0x00, 0x5348); // SH - more magic WriteLong(Memory, address + 0x02, argc); WriteLong(Memory, address + 0x06, argvAddress); WriteLong(Memory, address + 0x0a, envpAddress); // possible the application exit code... WriteLong(Memory, address + 0x0e, 0x00007000); WriteLong(Memory, address + 0x12, 0x00008000); WriteLong(Memory, address + 0x16, 0x00009000); WriteWord(Memory, address + 0x1a, 0x0190); // ???? // default file table? block of size 0x3c. // copied into IntEnv+0x1c // _initIOPtable WriteLong(Memory, address + 0x1c, 0x0000a000); // stdin WriteLong(Memory, 0x0000a000+0, 0x00010000); // ??? WriteLong(Memory, 0x0000a000+4, 0x00003000); // type ptr (FSYS) WriteLong(Memory, 0x0000a000+8, STDIN_FILENO); // ptr to refcount/fd struct? WriteLong(Memory, 0x0000a000+12, 0x00000000); // transferCount WriteLong(Memory, 0x0000a000+16, 0x00000000); // transferBuffer // stdout //0x0001 = readable //0x0002 = writable // others?... WriteLong(Memory, 0x0000a000+20, 0x00020000); // ??? {uint16_t flags, uint16_t error? } WriteLong(Memory, 0x0000a000+24, 0x00003000); // type ptr (FSYS) WriteLong(Memory, 0x0000a000+28, STDOUT_FILENO); // ptr to refcount/fd struct? WriteLong(Memory, 0x0000a000+32, 0x00000000); //??? WriteLong(Memory, 0x0000a000+36, 0x00000000); //??? // stderr WriteLong(Memory, 0x0000a000+40, 0x00020000); // ??? WriteLong(Memory, 0x0000a000+44, 0x00003000); // type ptr (FSYS) WriteLong(Memory, 0x0000a000+48, STDERR_FILENO); // ptr to refcount/fd struct? WriteLong(Memory, 0x0000a000+52, 0x00000000); //??? WriteLong(Memory, 0x0000a000+56, 0x00000000); //??? WriteWord(Memory, 0xf000, MPW::fQuit); WriteWord(Memory, 0xf002, 0x4E75); // rts WriteWord(Memory, 0xf004, MPW::fAccess); WriteWord(Memory, 0xf006, 0x4E75); // rts WriteWord(Memory, 0xf008, MPW::fClose); WriteWord(Memory, 0xf00a, 0x4E75); // rts WriteWord(Memory, 0xf00c, MPW::fRead); WriteWord(Memory, 0xf00e, 0x4E75); // rts WriteWord(Memory, 0xf010, MPW::fWrite); WriteWord(Memory, 0xf012, 0x4E75); // rts WriteWord(Memory, 0xf014, MPW::fIOCtl); WriteWord(Memory, 0xf016, 0x4E75); // rts // StdDevs (0x78 bytes) // copied into a $78 byte buffer stored at _IntEnv+20 // this has pointers to read/write functions // (although the executable has it's own functions as well...) WriteLong(Memory, address + 0x20, 0x00003000); WriteLong(Memory, 0x00003000+0, 0x46535953); // 'FSYS' WriteLong(Memory, 0x00003000+4, 0xf004); //access WriteLong(Memory, 0x00003000+8, 0xf008); // close WriteLong(Memory, 0x00003000+12, 0xf00c); // read WriteLong(Memory, 0x00003000+16, 0xf010); // write WriteLong(Memory, 0x00003000+20, 0xf014); // ioctl WriteLong(Memory, 0x00003000+24, 0x45434f4e); // 'ECON' WriteLong(Memory, 0x00003000+28, 0); //access WriteLong(Memory, 0x00003000+32, 0); // close WriteLong(Memory, 0x00003000+36, 0); // read WriteLong(Memory, 0x00003000+40, 0); // write WriteLong(Memory, 0x00003000+44, 0); // ioctl WriteLong(Memory, 0x00003000+48, 0x53595354); // 'SYST' WriteLong(Memory, 0x00003000+52, 0); //access WriteLong(Memory, 0x00003000+56, 0); // close WriteLong(Memory, 0x00003000+60, 0); // read WriteLong(Memory, 0x00003000+68, 0); // write WriteLong(Memory, 0x00003000+72, 0); // ioctl WriteLong(Memory, 0x00003000+76, 0); WriteLong(Memory, 0x00003000+80, 0); WriteLong(Memory, 0x00003000+84, 0); WriteLong(Memory, 0x00003000+88, 0); WriteLong(Memory, 0x00003000+92, 0); WriteLong(Memory, 0x00003000+96, 0); WriteLong(Memory, 0x00003000+100, 0); WriteLong(Memory, 0x00003000+104, 0); WriteLong(Memory, 0x00003000+108, 0); WriteLong(Memory, 0x00003000+112, 0); WriteLong(Memory, 0x00003000+116, 0); // _RTInit fills in this location with &_IntEnv. WriteLong(Memory, address + 0x24, 0x00004000); WriteLong(Memory, address + 0x28, 0x00005000); WriteLong(Memory, address + 0x2c, 0x00006000); // 0x1c should have something, too .... :( // 0x031a - Lo3Bytes WriteLong(Memory, 0x031a, 0x00ffffff); // 0x0a02 - OneOne WriteLong(Memory, 0x0a02, 0x00010001); // 0x0a06 - MinusOne WriteLong(Memory, 0x0a06, 0xffffffff); // todo -- expects high stack, low heap. // the allocator currently works from the top down, // so put stack at top of memory? // 0x0130 -- ApplLimit WriteLong(Memory, 0x0130, Flags.ram - 1); } extern "C" { const char *TrapName(uint16_t trap); } void LogToolBox(uint32_t pc, uint16_t trap) { const char *name; name = TrapName(trap); if (name) { fprintf(stderr, "$%08X %-51s ; %04X\n", pc, name, trap); } else { fprintf(stderr, "$%08X Tool #$%04X ; %04X\n", pc, trap, trap); } } void InstructionLogger() { static char strings[4][256]; for (unsigned j = 0; j < 4; ++j) strings[j][0] = 0; uint32_t pc = cpuGetPC(); uint16_t opcode = ReadWord(Memory, pc); if ((opcode & 0xf000) == 0xa000) { LogToolBox(pc, opcode); return; } #if 0 fprintf(stderr, "D: %08x %08x %08x %08x %08x %08x %08x %08x\n", cpuGetDReg(0), cpuGetDReg(1), cpuGetDReg(2), cpuGetDReg(3), cpuGetDReg(4), cpuGetDReg(5), cpuGetDReg(6), cpuGetDReg(7) ); fprintf(stderr, "A: %08x %08x %08x %08x %08x %08x %08x %08x\n", cpuGetAReg(0), cpuGetAReg(1), cpuGetAReg(2), cpuGetAReg(3), cpuGetAReg(4), cpuGetAReg(5), cpuGetAReg(6), cpuGetAReg(7) ); #endif if (Flags.traceCPU) { cpuDisOpcode(pc, strings[0], strings[1], strings[2], strings[3]); // address, data, instruction, operand fprintf(stderr, "%s %-10s %-40s ; %s\n", strings[0], strings[2], strings[3], strings[1]); // todo -- trace registers (only print changed registers?) #if 0 if (pc >= 0x00010E94 && pc <= 0x00010FC0) { fprintf(stderr, "d7 = %08x\n", cpuGetDReg(7)); } #endif } if (opcode == 0x4E75 || opcode == 0x4ED0) // RTS or JMP (A0) { // check for MacsBug name after rts. std::string s; unsigned b = memoryReadByte(pc + 2); if (b > 0x80 && b < 0xa0) { b -= 0x80; pc += 3; s.reserve(b); for (unsigned i = 0; i < b; ++i) { s.push_back(memoryReadByte(pc++)); } fprintf(stderr, "%s\n\n", s.c_str()); } } } void help() { } bool parse_number(const char *input, uint32_t *dest) { char *end; long value; int base = 0; // octal is dumb so don't allow it. while (isspace(*input)) ++input; if (*input == '0' && isdigit(input[1])) base = 10; errno = 0; value = strtol(input, &end, base); if (errno || value < 0 || input == end) { fprintf(stderr, "%s - invalid input\n", input); return false; } // M/K if (*end) { int old = value; if (strcasecmp(end, "M") == 0) value *= 1024 * 1024; else if (strcasecmp(end, "K") == 0) value *= 1024; else { fprintf(stderr, "%s - invalid input\n", input); return false; } if (value < old) { // overflow fprintf(stderr, "%s - invalid input\n", input); return false; } } if (dest) *dest = value; return true; } int main(int argc, char **argv) { // getopt... enum { kTraceCPU = 1, kTraceMacsBug, kTraceGlobals, }; static struct option LongOpts[] = { { "ram",required_argument, NULL, 'r' }, { "stack", required_argument, NULL, 's' }, { "machine", required_argument, NULL, 'm' }, { "trace-cpu", no_argument, NULL, kTraceCPU }, { "trace-macsbug", no_argument, NULL, kTraceMacsBug }, { "trace-globals", no_argument, NULL, kTraceGlobals }, { "help", no_argument, NULL, 'h' }, { "version", no_argument, NULL, 'V' }, { NULL, 0, NULL, 0 } }; int c; while ((c = getopt_long(argc, argv, "+hVm:r:s:", LongOpts, NULL)) != -1) { switch(c) { case kTraceCPU: Flags.traceCPU = true; break; case kTraceMacsBug: Flags.traceMacsbug = true; break; case kTraceGlobals: Flags.traceGlobals = true; break; case 'm': if (!parse_number(optarg, &Flags.machine)) exit(EX_CONFIG); break; case 'r': if (!parse_number(optarg, &Flags.ram)) exit(EX_CONFIG); break; case 's': if (!parse_number(optarg, &Flags.stack)) exit(EX_CONFIG); break; case ':': case '?': help(); exit(EX_USAGE); break; case 'h': help(); exit(EX_OK); break; case 'V': printf("mpw version 0"); exit(EX_OK); break; } } argc -= optind; argv += optind; if (!argc) { help(); exit(EX_USAGE); } Memory = new uint8_t[Flags.ram]; MemorySize = Flags.ram; cpuStartup(); cpuSetModel(3,0); uint32_t address = load(argv[0]); InitializeMPW(argc, argv); if (!Flags.stack) { fprintf(stderr, "Invalid stack size\n"); exit(EX_CONFIG); } std::pair StackRange; // allocate stack, set A7... { Flags.stack = (Flags.stack + 3) & ~0x03; uint32_t address = EmulatedNewPtr(Flags.stack); StackRange.first = address; StackRange.second = address + Flags.stack; // address grows down // -4 is for the return address. cpuSetAReg(7, address + Flags.stack - 4); // return address. WriteLong(Memory, address + Flags.stack - 4, 0x0a06); // MinusOne global :) } cpuSetALineExceptionFunc(ToolBox::dispatch); cpuSetFLineExceptionFunc(MPW::dispatch); /// ahhh... need to set PC after memory. // for pre-fetch. memorySetMemory(Memory, MemorySize); if (Flags.traceGlobals) memorySetGlobalLog(0x3000); cpuInitializeFromNewPC(address); MM::Init(Memory, MemorySize, HighWater); if (Flags.traceCPU || Flags.traceMacsbug) { cpuSetInstructionLoggingFunc(InstructionLogger); } for (;;) { uint32_t pc = cpuGetPC(); if (pc == 0x00000000) break; if (cpuGetStop()) break; cpuExecuteInstruction(); } // return value in mpwblock + 0x0e? exit(0); }