mirror of
https://github.com/ksherlock/mpw.git
synced 2024-11-29 15:49:31 +00:00
593 lines
12 KiB
C++
593 lines
12 KiB
C++
/*
|
|
* Copyright (c) 2013, Kelvin W Sherlock
|
|
* All rights reserved.
|
|
*
|
|
* Redistribution and use in source and binary forms, with or without
|
|
* modification, are permitted provided that the following conditions are met:
|
|
*
|
|
* 1. Redistributions of source code must retain the above copyright notice, this
|
|
* list of conditions and the following disclaimer.
|
|
* 2. Redistributions in binary form must reproduce the above copyright notice,
|
|
* this list of conditions and the following disclaimer in the documentation
|
|
* and/or other materials provided with the distribution.
|
|
*
|
|
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
|
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
|
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
|
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
|
|
* ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
|
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
|
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
|
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
|
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
|
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
*
|
|
*/
|
|
|
|
#include <string>
|
|
#include <cstring>
|
|
#include <cassert>
|
|
#include <vector>
|
|
#include <algorithm>
|
|
#include <unordered_map>
|
|
|
|
#include <cpu/defs.h>
|
|
#include <cpu/CpuModule.h>
|
|
#include <cpu/fmem.h>
|
|
|
|
#include "loader.h"
|
|
#include "toolbox.h"
|
|
#include "stackframe.h"
|
|
|
|
#include "rm.h"
|
|
#include "mm.h"
|
|
|
|
#include <macos/sysequ.h>
|
|
#include <macos/tool_return.h>
|
|
|
|
using MacOS::tool_return;
|
|
|
|
enum {
|
|
fsCurPerm = 0x00, /* open access permissions in ioPermssn */
|
|
fsRdPerm = 0x01,
|
|
fsWrPerm = 0x02,
|
|
fsRdWrPerm = 0x03,
|
|
fsRdWrShPerm = 0x04,
|
|
fsRdDenyPerm = 0x10, /* for use with OpenDeny and OpenRFDeny */
|
|
fsWrDenyPerm = 0x20 /* for use with OpenDeny and OpenRFDeny */
|
|
};
|
|
|
|
using ToolBox::Log;
|
|
|
|
namespace Loader {
|
|
|
|
namespace {
|
|
|
|
const uint32_t kCODE = 0x434f4445;
|
|
|
|
struct SegmentInfo
|
|
{
|
|
SegmentInfo()
|
|
{}
|
|
|
|
SegmentInfo(uint32_t h, uint32_t a, uint32_t s, bool fm = false):
|
|
handle(h), address(a), size(s), farModel(fm)
|
|
{}
|
|
|
|
uint32_t handle = 0;
|
|
uint32_t address = 0;
|
|
uint32_t size = 0;
|
|
bool farModel = false;
|
|
// todo -- also add std::string segmentName?
|
|
};
|
|
|
|
struct Segment0Info
|
|
{
|
|
Segment0Info()
|
|
{}
|
|
uint32_t a5 = 0;
|
|
uint32_t jtOffset = 0;
|
|
uint32_t jtSize = 0;
|
|
uint32_t jtStart = 0;
|
|
uint32_t jtEnd = 0;
|
|
};
|
|
|
|
std::vector<SegmentInfo> Segments;
|
|
|
|
void reloc1(const uint8_t *r, uint32_t address, uint32_t offset)
|
|
{
|
|
// %00000000 00000000 -> break
|
|
// %0xxxxxxx -> 7-bit value
|
|
// %1xxxxxxx xxxxxxxx -> 15-bit value
|
|
// %00000000 1xxxxxxx x{8} x{8} x{8} -> 31 bit value
|
|
// ^ that's what the documentation says..
|
|
// that's how the 32-bit bootstrap works
|
|
// DumpCode ignores the high 2 bytes.
|
|
for(;;)
|
|
{
|
|
uint32_t x;
|
|
uint32_t value;
|
|
|
|
x = *r++;
|
|
|
|
if (x == 0x00)
|
|
{
|
|
x = *r++;
|
|
if (x == 0x00) break;
|
|
|
|
x = (x << 8) | *r++;
|
|
x = (x << 8) | *r++;
|
|
x = (x << 8) | *r++;
|
|
}
|
|
else if (x & 0x80)
|
|
{
|
|
x &= 0x7f;
|
|
x = (x << 8) | *r++;
|
|
}
|
|
|
|
x <<= 1; // * 2
|
|
|
|
address += x;
|
|
|
|
value = memoryReadLong(address);
|
|
memoryWriteLong(value + offset, address);
|
|
}
|
|
|
|
}
|
|
|
|
// relocate a far model segment.
|
|
void relocate(uint32_t address, uint32_t size, uint32_t a5)
|
|
{
|
|
// see MacOS RT Architecture, 10-23 .. 10-26
|
|
uint32_t offset;
|
|
|
|
offset = memoryReadLong(address + 0x14);
|
|
if (memoryReadLong(address + 0x18) != a5 && offset != 0)
|
|
{
|
|
memoryWriteLong(a5, address + 0x18); // current value of A5
|
|
reloc1(memoryPointer(address + offset), address, a5);
|
|
}
|
|
|
|
offset = memoryReadLong(address + 0x1c);
|
|
if (memoryReadLong(address + 0x20) != address && offset != 0)
|
|
{
|
|
memoryWriteLong(address, address + 0x20); // segment load address.
|
|
reloc1(memoryPointer(address + offset), address, address + 0x28);
|
|
}
|
|
}
|
|
|
|
|
|
// load code seg 0.
|
|
tool_return<void> LoadCode0(Segment0Info &rv)
|
|
{
|
|
uint32_t rHandle;
|
|
uint32_t size;
|
|
uint32_t address;
|
|
|
|
SegmentInfo si;
|
|
|
|
{
|
|
auto tmp = RM::Native::GetResource(kCODE, 0);
|
|
if (tmp.error()) return tmp.error();
|
|
rHandle = tmp.value();
|
|
}
|
|
|
|
MM::Native::HLock(rHandle);
|
|
{
|
|
auto tmp = MM::Native::GetHandleInfo(rHandle);
|
|
address = tmp.value().address;
|
|
size = tmp.value().size;
|
|
}
|
|
|
|
uint32_t above = memoryReadLong(address);
|
|
uint32_t below = memoryReadLong(address + 4);
|
|
rv.jtSize = memoryReadLong(address + 8);
|
|
rv.jtOffset = memoryReadLong(address + 12);
|
|
|
|
si.size = above + below;
|
|
|
|
|
|
// create a new handle for the a5 segment.
|
|
{
|
|
auto tmp = MM::Native::NewHandle(si.size, true);
|
|
if (tmp.error()) return tmp.error();
|
|
si.handle = tmp.value().handle;
|
|
si.address = tmp.value().pointer;
|
|
}
|
|
|
|
MM::Native::HLock(si.handle);
|
|
|
|
rv.a5 = si.address + below;
|
|
|
|
// copy jump table data from the CODE segment
|
|
// to the new handle.
|
|
std::memcpy(memoryPointer(rv.a5 + rv.jtOffset), memoryPointer(address + 16), rv.jtSize);
|
|
|
|
if (Segments.size() <= 0)
|
|
Segments.resize(0 + 1);
|
|
Segments[0] = si;
|
|
|
|
rv.jtStart = rv.a5 + rv.jtOffset;
|
|
rv.jtEnd = rv.jtStart + rv.jtSize;
|
|
|
|
// TODO -- should ReleaseResource on rHandle.
|
|
return MacOS::noErr;
|
|
}
|
|
|
|
// load a standard code segment.
|
|
tool_return<void> LoadCode(uint16_t segment)
|
|
{
|
|
|
|
SegmentInfo si;
|
|
|
|
{
|
|
auto tmp = RM::Native::GetResource(kCODE, segment);
|
|
if (tmp.error()) return tmp.error();
|
|
si.handle = tmp.value();
|
|
}
|
|
MM::Native::HLock(si.handle);
|
|
|
|
{
|
|
auto tmp = MM::Native::GetHandleInfo(si.handle);
|
|
si.size = tmp.value().size;
|
|
si.address = tmp.value().address;
|
|
}
|
|
|
|
if (memoryReadWord(si.address) == 0xffff)
|
|
si.farModel = true;
|
|
|
|
if (Segments.size() <= segment)
|
|
Segments.resize(segment + 1);
|
|
|
|
Segments[segment] = si;
|
|
|
|
return MacOS::noErr;
|
|
}
|
|
|
|
|
|
}
|
|
|
|
namespace Native
|
|
{
|
|
|
|
tool_return<void> LoadFile(const std::string &path)
|
|
{
|
|
|
|
tool_return<void> err;
|
|
|
|
// open the file
|
|
// load code seg 0
|
|
// iterate and load other segments
|
|
|
|
auto tmp = RM::Native::OpenResFile(path, fsRdPerm);
|
|
if (tmp.error()) return tmp.error();
|
|
|
|
// in case of restart?
|
|
Segments.clear();
|
|
|
|
RM::Native::SetResLoad(true);
|
|
|
|
// load code 0.
|
|
Segment0Info seg0;
|
|
err = LoadCode0(seg0);
|
|
if (!err) return err;
|
|
|
|
// iterate through the jump table to get the other
|
|
// code segments to load
|
|
bool farModel = false;
|
|
for (uint32_t jtEntry = seg0.jtStart; jtEntry < seg0.jtEnd; jtEntry += 8)
|
|
{
|
|
uint16_t seg;
|
|
uint32_t offset;
|
|
|
|
if (farModel)
|
|
{
|
|
seg = memoryReadWord(jtEntry + 0);
|
|
offset = memoryReadLong(jtEntry + 4);
|
|
|
|
assert(memoryReadWord(jtEntry + 2) == 0xA9F0);
|
|
}
|
|
else
|
|
{
|
|
if (memoryReadWord(jtEntry + 2) == 0xffff)
|
|
{
|
|
farModel = true;
|
|
continue;
|
|
}
|
|
|
|
offset = memoryReadWord(jtEntry + 0);
|
|
seg = memoryReadWord(jtEntry + 4);
|
|
|
|
assert(memoryReadWord(jtEntry + 2) == 0x3F3C);
|
|
assert(memoryReadWord(jtEntry + 6) == 0xA9F0);
|
|
}
|
|
|
|
// load, if necessary.
|
|
assert(seg);
|
|
|
|
if (seg >= Segments.size() || Segments[seg].address == 0)
|
|
{
|
|
err = LoadCode(seg);
|
|
if (!err) return err;
|
|
|
|
const auto &p = Segments[seg];
|
|
if (p.farModel)
|
|
{
|
|
relocate(p.address, p.size, seg0.a5);
|
|
}
|
|
}
|
|
|
|
const auto &p = Segments[seg];
|
|
|
|
assert(p.address); // missing segment?!
|
|
assert(offset < p.size);
|
|
|
|
// +$4/$28 for the jump table info header.
|
|
uint32_t address = p.address + offset + (p.farModel ? 0x00 : 0x04); // was 0x28
|
|
|
|
if (!p.farModel)
|
|
memoryWriteWord(seg, jtEntry + 0);
|
|
|
|
memoryWriteWord(0x4EF9, jtEntry + 2);
|
|
memoryWriteLong(address, jtEntry + 4);
|
|
}
|
|
|
|
// seg:16, jmp:16, address:32
|
|
uint32_t pc = memoryReadLong(seg0.jtStart + 4);
|
|
|
|
cpuSetAReg(5, seg0.a5);
|
|
cpuInitializeFromNewPC(pc);
|
|
|
|
|
|
// 0x0934 - CurJTOffset (16-bit)
|
|
memoryWriteWord(seg0.jtOffset, MacOS::CurJTOffset);
|
|
|
|
// 0x0904 -- CurrentA5 (32-bit)
|
|
memoryWriteLong(seg0.a5, MacOS::CurrentA5);
|
|
|
|
// 0x0910 CurApName (31-char max pstring.)
|
|
{
|
|
std::string s;
|
|
|
|
auto ix = path.find_last_of("/:", 0, 2);
|
|
if (ix == path.npos)
|
|
{
|
|
s = path.substr(0, 31);
|
|
}
|
|
else
|
|
{
|
|
s = path.substr(ix + 1, 31);
|
|
}
|
|
|
|
ToolBox::WritePString(MacOS::CurApName, s);
|
|
}
|
|
|
|
|
|
|
|
return MacOS::noErr;
|
|
}
|
|
|
|
|
|
|
|
//
|
|
void LoadDebugNames(DebugNameTable &table)
|
|
{
|
|
|
|
if (Segments.empty()) return;
|
|
|
|
|
|
// skip segment 0 since that's the dispatch table.
|
|
for (unsigned seg = 1; seg < Segments.size(); ++seg)
|
|
{
|
|
|
|
const auto &si = Segments[seg];
|
|
const uint8_t *memory = memoryPointer(si.address);
|
|
unsigned size = si.size;
|
|
|
|
// store entry for start?
|
|
// SEG_## = pc ?
|
|
|
|
|
|
// See MacsBugs appendix D.
|
|
|
|
/*
|
|
* format is:
|
|
* (optional) LINK A6, [$4e56]
|
|
* opcodes...
|
|
* RTS/JMP (A0)/RTD
|
|
* procedure name
|
|
* procedure constants
|
|
*
|
|
* name is fixed at 8 or 16 bytes or variable length.
|
|
* valid characters = [a-zA-Z0-9_%.] and space, to pad fixed length names.
|
|
*
|
|
*/
|
|
|
|
uint32_t start = 0;
|
|
for (unsigned pc = 0; pc < size; )
|
|
{
|
|
bool eof = false; // end of function
|
|
uint32_t oldpc = pc;
|
|
|
|
uint16_t opcode = (memory[pc + 0] << 8) | memory[pc + 1];
|
|
pc += 2;
|
|
|
|
switch(opcode)
|
|
{
|
|
case 0x4E56:
|
|
// link A6,#
|
|
start = oldpc;
|
|
break;
|
|
|
|
case 0x4E75: // rts
|
|
case 0x4ED0: // jmp (a0)
|
|
eof = true;
|
|
break;
|
|
case 0x4E74: // rtd #
|
|
pc += 2; // skip the argument.
|
|
eof = true;
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
|
|
if (!eof) continue;
|
|
|
|
std::string s;
|
|
|
|
unsigned length = memory[pc];
|
|
|
|
// variable length.
|
|
if (length >= 0x80 && length <= 0x9f)
|
|
{
|
|
length &= 0x7f;
|
|
++pc;
|
|
|
|
if (length == 0)
|
|
length = memory[pc++];
|
|
|
|
s.assign((const char *)memory + pc, length);
|
|
|
|
// align to word boundary
|
|
pc = (pc + length + 1) & ~1;
|
|
}
|
|
else
|
|
{
|
|
// fixed length. high byte may or may not be set.
|
|
// if high byte of second char is set, it's a 16-char string.
|
|
|
|
length = 8;
|
|
s.assign((const char *)memory + pc, 8);
|
|
s[0] &= 0x7f;
|
|
|
|
while (s.length() && s.back() == ' ') s.pop_back();
|
|
|
|
if ((s.length() >= 2) && (s[1] & 0x80))
|
|
{
|
|
s[1] &= 0x7f;
|
|
length = 16;
|
|
|
|
std::string tmp((const char *)memory + pc + 8, 8);
|
|
while (tmp.length() && tmp.back() == ' ') tmp.pop_back();
|
|
|
|
tmp.push_back('.');
|
|
tmp.append(s);
|
|
s = std::move(tmp);
|
|
}
|
|
|
|
pc += length;
|
|
}
|
|
|
|
// verify name is legal.
|
|
bool ok = std::all_of(s.begin(), s.end(),
|
|
[](char c) {
|
|
if (c >= 'A' && c <= 'Z') return true;
|
|
if (c >= 'a' && c <= 'z') return true;
|
|
if (c >= '0' && c <= '9') return true;
|
|
if (c == '.') return true;
|
|
if (c == '_') return true;
|
|
if (c == '%') return true;
|
|
return false;
|
|
});
|
|
|
|
if (!ok)
|
|
{
|
|
// also set start.
|
|
|
|
start = pc = oldpc + 2;
|
|
if (opcode == 0x4E74) start += 2;
|
|
continue;
|
|
}
|
|
|
|
|
|
|
|
// constant data
|
|
length = (memory[pc + 0] << 8) | memory[pc + 1];
|
|
pc += 2;
|
|
if (length)
|
|
{
|
|
pc = (pc + length + 1) & ~1;
|
|
}
|
|
|
|
|
|
// TODO -- should this include the name and data?
|
|
table.emplace(std::move(s), std::make_pair(start + si.address, pc + si.address));
|
|
|
|
// in case no link instruction...
|
|
start = pc;
|
|
|
|
}
|
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
} // Internal namespace
|
|
|
|
|
|
/*
|
|
* struct { uint32_t a5; uint32_t pc; };
|
|
* future state: Native::LoadFile(const std::string &path, a5pc) ->
|
|
* load file, set global page variables, set A5, return PC.
|
|
*
|
|
*/
|
|
|
|
|
|
uint16_t UnloadSeg(uint16_t trap)
|
|
{
|
|
// UnloadSeg (routineAddr: Ptr);
|
|
|
|
/*
|
|
* ------------
|
|
* +0 routineAddr
|
|
* ------------
|
|
*
|
|
*/
|
|
|
|
uint32_t sp;
|
|
uint32_t routineAddr;
|
|
|
|
sp = StackFrame<4>(routineAddr);
|
|
|
|
Log("%04x UnloadSeg(%08x)\n", trap, routineAddr);
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
uint16_t LoadSeg(uint16_t trap) {
|
|
|
|
|
|
/*
|
|
* Jump Table Entry:
|
|
*
|
|
* Unloaded:
|
|
* +0 offset
|
|
* +2 instruction that pushes segment number onto stack
|
|
* +6 LoadSeg trap
|
|
*
|
|
* Loaded:
|
|
* +0 offset
|
|
* +2 jump instruction.
|
|
*
|
|
*/
|
|
|
|
|
|
|
|
uint32_t sp;
|
|
uint16_t segment;
|
|
|
|
sp = StackFrame<2>(segment);
|
|
|
|
Log("%04x LoadSeg(%04x)\n", trap, segment);
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
}
|