First public commit.
This commit is contained in:
parent
2a48f52979
commit
4bac9d83ed
288 changed files with 18417 additions and 1076 deletions
0
lenser/sms/__init__.py
Normal file
0
lenser/sms/__init__.py
Normal file
19
lenser/sms/convert/__init__.py
Normal file
19
lenser/sms/convert/__init__.py
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
"""Sega Master System conversion dispatch."""
|
||||
from __future__ import annotations
|
||||
|
||||
from ... import imageprep
|
||||
from . import bg, mono
|
||||
|
||||
_MODULES = {"bg": bg, "mono": mono}
|
||||
MODES = list(_MODULES.keys())
|
||||
|
||||
|
||||
def convert_image(path_or_img, mode="bg", palette_name="sms",
|
||||
dither_mode="floyd", intensive=False, prep_opt=None,
|
||||
base_color=None):
|
||||
prep_opt = prep_opt or imageprep.PrepOptions()
|
||||
module = _MODULES.get(mode, bg)
|
||||
img_rgb = imageprep.prepare(path_or_img, module.WIDTH, module.HEIGHT,
|
||||
module.PIXEL_ASPECT, prep_opt, border_rgb=(0, 0, 0))
|
||||
return module.convert(img_rgb, palette_name, dither_mode, intensive,
|
||||
base_color=base_color)
|
||||
150
lenser/sms/convert/_common.py
Normal file
150
lenser/sms/convert/_common.py
Normal file
|
|
@ -0,0 +1,150 @@
|
|||
"""Sega Master System background encoder: 256x192, 2 palettes of 16 (of 64),
|
||||
per-8x8-tile palette select, <=512 tiles in VRAM.
|
||||
|
||||
Each tile is 4bpp (16 colours) and picks one of two 16-colour palettes, so up to
|
||||
32 colours on screen -- far less constrained than the NES. We pick palette 0 for
|
||||
the whole image, palette 1 for the colours it serves worst, assign each tile its
|
||||
better palette, dither, then vector-quantise the 8x8 patterns to 512 tiles.
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import numpy as np
|
||||
|
||||
from ... import dither, palette as c64pal
|
||||
from ...convert.base import perceptual_error
|
||||
from .. import palette as smspal
|
||||
|
||||
W, H = 256, 192
|
||||
TCOLS, TROWS = 32, 24
|
||||
# VRAM is 16K: pattern table $0000-$37FF (448 tiles), name table $3800, sprite
|
||||
# attribute table $3F00 -- so at most 448 unique background tiles.
|
||||
NTILES = 448
|
||||
|
||||
|
||||
def _choose(img_lab, plab, n, weight=None):
|
||||
flat = img_lab.reshape(-1, 3)
|
||||
d = np.sum((flat[:, None, :] - plab[None, :, :]) ** 2, axis=-1) # (px,64)
|
||||
if weight is not None:
|
||||
d = d * weight[:, None]
|
||||
chosen, best = [], np.full(len(flat), np.inf)
|
||||
for _ in range(n):
|
||||
cand = np.minimum(best[:, None], d).sum(0)
|
||||
for c in chosen:
|
||||
cand[c] = np.inf
|
||||
c = int(cand.argmin())
|
||||
chosen.append(c)
|
||||
best = np.minimum(best, d[:, c])
|
||||
return sorted(chosen)
|
||||
|
||||
|
||||
def _tile_codebook(patterns, k, iters=8):
|
||||
uniq, counts = np.unique(patterns, axis=0, return_counts=True)
|
||||
if len(uniq) <= k:
|
||||
code = np.zeros((k, patterns.shape[1]), patterns.dtype)
|
||||
code[:len(uniq)] = uniq
|
||||
lut = {tuple(p): i for i, p in enumerate(uniq)}
|
||||
return code, np.array([lut[tuple(p)] for p in patterns])
|
||||
code = uniq[np.argsort(-counts)[:k]].copy()
|
||||
labels = np.zeros(len(patterns), np.int64)
|
||||
for _ in range(iters):
|
||||
for s in range(0, len(patterns), 2048):
|
||||
blk = patterns[s:s + 2048]
|
||||
labels[s:s + 2048] = (blk[:, None, :] != code[None]).sum(2).argmin(1)
|
||||
moved = False
|
||||
for j in range(k):
|
||||
mem = patterns[labels == j]
|
||||
if len(mem):
|
||||
med = np.array([np.bincount(mem[:, p], minlength=16).argmax()
|
||||
for p in range(mem.shape[1])], patterns.dtype)
|
||||
if not np.array_equal(med, code[j]):
|
||||
code[j] = med; moved = True
|
||||
if not moved:
|
||||
break
|
||||
for s in range(0, len(patterns), 2048):
|
||||
blk = patterns[s:s + 2048]
|
||||
labels[s:s + 2048] = (blk[:, None, :] != code[None]).sum(2).argmin(1)
|
||||
return code, labels
|
||||
|
||||
|
||||
def _palettes(img_lab, mono, base_color):
|
||||
plab = smspal.palette_lab()
|
||||
if mono:
|
||||
greys = sorted(smspal.GREYS, key=lambda i: plab[i, 0])
|
||||
pal0 = (greys * 4)[:16] # 4 greys, padded to 16
|
||||
return [pal0, pal0]
|
||||
pal0 = _choose(img_lab, plab, 16)
|
||||
# palette 1 covers the colours palette 0 reproduces worst
|
||||
flat = img_lab.reshape(-1, 3)
|
||||
resid = np.min(np.sum((flat[:, None, :] - plab[pal0][None]) ** 2, 2), 1)
|
||||
pal1 = _choose(img_lab, plab, 16, weight=resid)
|
||||
return [pal0, pal1]
|
||||
|
||||
|
||||
def encode(img_rgb, dither_mode, mono=False, base_color=None):
|
||||
plab = smspal.palette_lab()
|
||||
prgb = smspal.get_palette().astype(np.uint8)
|
||||
img_lab = c64pal.srgb_to_lab(img_rgb)
|
||||
pals = _palettes(img_lab, mono, base_color) # 2 x 16 indices
|
||||
pal_idx = np.array(pals) # (2,16)
|
||||
plab_pal = plab[pal_idx] # (2,16,3)
|
||||
|
||||
# assign each tile the palette (0/1) with lower nearest-colour error
|
||||
tile_pal = np.zeros((TROWS, TCOLS), np.int64)
|
||||
for ty in range(TROWS):
|
||||
for tx in range(TCOLS):
|
||||
blk = img_lab[ty * 8:ty * 8 + 8, tx * 8:tx * 8 + 8].reshape(-1, 3)
|
||||
e0 = np.min(np.sum((blk[:, None, :] - plab_pal[0][None]) ** 2, 2), 1).sum()
|
||||
e1 = np.min(np.sum((blk[:, None, :] - plab_pal[1][None]) ** 2, 2), 1).sum()
|
||||
tile_pal[ty, tx] = 0 if e0 <= e1 else 1
|
||||
|
||||
# per-pixel allowed = its tile's 16 palette colours (global index 0-31); dither
|
||||
plab32 = plab[pal_idx.reshape(-1)] # (32,3)
|
||||
allowed = np.zeros((H, W, 16), np.int64)
|
||||
for ty in range(TROWS):
|
||||
for tx in range(TCOLS):
|
||||
base = tile_pal[ty, tx] * 16
|
||||
allowed[ty * 8:ty * 8 + 8, tx * 8:tx * 8 + 8] = np.arange(base, base + 16)
|
||||
idx = dither.quantize(img_lab, allowed, plab32, dither_mode).astype(np.int64)
|
||||
pen = (idx - np.repeat(np.repeat(tile_pal, 8, 0), 8, 1) * 16).astype(np.uint8)
|
||||
|
||||
# 8x8 tiles -> patterns (pen 0-15); vector-quantise to <=512
|
||||
tiles = pen.reshape(TROWS, 8, TCOLS, 8).transpose(0, 2, 1, 3).reshape(TROWS * TCOLS, 64)
|
||||
code, labels = _tile_codebook(tiles, NTILES)
|
||||
name_pat = labels.reshape(TROWS, TCOLS)
|
||||
|
||||
# ---- emit VDP data ----
|
||||
patterns = bytearray(NTILES * 32)
|
||||
for t in range(NTILES):
|
||||
pat = code[t].reshape(8, 8)
|
||||
for r in range(8):
|
||||
for k in range(4):
|
||||
byte = 0
|
||||
for x in range(8):
|
||||
byte |= ((int(pat[r, x]) >> k) & 1) << (7 - x)
|
||||
patterns[t * 32 + r * 4 + k] = byte
|
||||
|
||||
nametable = bytearray(TROWS * TCOLS * 2)
|
||||
for ty in range(TROWS):
|
||||
for tx in range(TCOLS):
|
||||
entry = (int(name_pat[ty, tx]) & 0x1FF) | (int(tile_pal[ty, tx]) << 11)
|
||||
o = (ty * TCOLS + tx) * 2
|
||||
nametable[o] = entry & 0xFF
|
||||
nametable[o + 1] = (entry >> 8) & 0xFF
|
||||
|
||||
palette = bytes(int(c) for c in pal_idx.reshape(-1)) # 32 colour indices (0-63)
|
||||
|
||||
# rebuild displayed image (clustered tiles + per-tile palette) for preview
|
||||
disp = code[labels].reshape(TROWS, TCOLS, 8, 8).transpose(0, 2, 1, 3).reshape(H, W)
|
||||
final = np.zeros((H, W), np.uint16)
|
||||
for ty in range(TROWS):
|
||||
for tx in range(TCOLS):
|
||||
ys, xs = slice(ty * 8, ty * 8 + 8), slice(tx * 8, tx * 8 + 8)
|
||||
final[ys, xs] = pal_idx[tile_pal[ty, tx]][disp[ys, xs]]
|
||||
|
||||
if mono:
|
||||
lum = img_lab.copy(); lum[..., 1:] = 0.0
|
||||
pl = plab.copy(); pl[:, 1:] = 0.0
|
||||
err = perceptual_error(final, lum, pl)
|
||||
else:
|
||||
err = perceptual_error(final, img_lab, plab)
|
||||
return bytes(patterns), bytes(nametable), palette, prgb[final], err
|
||||
19
lenser/sms/convert/bg.py
Normal file
19
lenser/sms/convert/bg.py
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
"""SMS background image: 256x192, 2 palettes of 16 (of 64), <=512 tiles."""
|
||||
from __future__ import annotations
|
||||
|
||||
from ...convert.base import Conversion
|
||||
from . import _common
|
||||
|
||||
WIDTH, HEIGHT = 256, 192
|
||||
PIXEL_ASPECT = 1.0 # 256x192 is exactly 4:3 -> square pixels
|
||||
|
||||
|
||||
def convert(img_rgb, palette_name="sms", dither_mode="floyd",
|
||||
intensive=False, base_color=None):
|
||||
pat, nt, pal, preview, err = _common.encode(img_rgb, dither_mode, mono=False)
|
||||
return Conversion(
|
||||
mode="bg", width=WIDTH, height=HEIGHT, pixel_aspect=PIXEL_ASPECT,
|
||||
index_image=None, data={"patterns": pat, "nametable": nt, "palette": pal},
|
||||
data_addr=0, viewer="sms", preview_rgb=preview, error=err,
|
||||
meta={"palette": "sms", "dither": dither_mode},
|
||||
)
|
||||
21
lenser/sms/convert/mono.py
Normal file
21
lenser/sms/convert/mono.py
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
"""SMS monochrome: 256x192 using the VDP's 4 true greys (2-bit per channel),
|
||||
tone carried by dithering."""
|
||||
from __future__ import annotations
|
||||
|
||||
from ...convert.base import Conversion
|
||||
from . import _common
|
||||
|
||||
WIDTH, HEIGHT = 256, 192
|
||||
PIXEL_ASPECT = 1.0
|
||||
|
||||
|
||||
def convert(img_rgb, palette_name="sms", dither_mode="floyd",
|
||||
intensive=False, base_color=None):
|
||||
pat, nt, pal, preview, err = _common.encode(img_rgb, dither_mode, mono=True,
|
||||
base_color=base_color)
|
||||
return Conversion(
|
||||
mode="mono", width=WIDTH, height=HEIGHT, pixel_aspect=PIXEL_ASPECT,
|
||||
index_image=None, data={"patterns": pat, "nametable": nt, "palette": pal},
|
||||
data_addr=0, viewer="sms", preview_rgb=preview, error=err,
|
||||
meta={"palette": "sms", "dither": dither_mode},
|
||||
)
|
||||
13
lenser/sms/exporter.py
Normal file
13
lenser/sms/exporter.py
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
"""Build a Sega Master System .sms cartridge from a conversion."""
|
||||
from __future__ import annotations
|
||||
|
||||
from . import viewer
|
||||
|
||||
|
||||
def export_sms(conv, output_path, source_path=None, display="forever",
|
||||
seconds=0, video="ntsc"):
|
||||
if not output_path.lower().endswith((".sms", ".bin")):
|
||||
output_path += ".sms"
|
||||
d = conv.data
|
||||
rom = viewer.build_rom(d["patterns"], d["nametable"], d["palette"])
|
||||
return viewer.write_sms(rom, output_path)
|
||||
33
lenser/sms/palette.py
Normal file
33
lenser/sms/palette.py
Normal file
|
|
@ -0,0 +1,33 @@
|
|||
"""Sega Master System VDP (315-5124) palette.
|
||||
|
||||
The SMS palette is 64 colours: each CRAM byte is %00BBGGRR -- 2 bits per channel
|
||||
(x85 -> 0,85,170,255). The colour index IS the byte written to CRAM, so PALETTE
|
||||
is indexed by hardware value 0-63. Two 16-colour palettes (background + sprite)
|
||||
are loaded; an image uses up to 32 colours on screen.
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import numpy as np
|
||||
|
||||
from ..palette import srgb_to_lab
|
||||
|
||||
|
||||
def _rgb(c):
|
||||
r = (c & 3) * 85
|
||||
g = ((c >> 2) & 3) * 85
|
||||
b = ((c >> 4) & 3) * 85
|
||||
return (r, g, b)
|
||||
|
||||
|
||||
PALETTE = np.array([_rgb(c) for c in range(64)], dtype=np.float64)
|
||||
|
||||
# greys: R==G==B -> byte where the three 2-bit fields are equal (0,21,42,63).
|
||||
GREYS = [c for c in range(64) if PALETTE[c, 0] == PALETTE[c, 1] == PALETTE[c, 2]]
|
||||
|
||||
|
||||
def get_palette() -> np.ndarray:
|
||||
return PALETTE
|
||||
|
||||
|
||||
def palette_lab() -> np.ndarray:
|
||||
return srgb_to_lab(PALETTE)
|
||||
109
lenser/sms/viewer.py
Normal file
109
lenser/sms/viewer.py
Normal file
|
|
@ -0,0 +1,109 @@
|
|||
"""Build a Sega Master System .sms cartridge: a Z80 VDP-setup program + data.
|
||||
|
||||
The Z80 code (org $0000) programs the VDP for mode 4 (256x192), uploads the tile
|
||||
patterns to VRAM $0000, the name table to $3800 and the palette to CRAM, turns
|
||||
the display on, then idles. A valid "TMR SEGA" header + checksum is written at
|
||||
$7FF0 so the export BIOS accepts the cartridge.
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
from .z80 import Asm
|
||||
|
||||
ROM_SIZE = 0x8000 # 32 KB (maps to $0000-$7FFF, no mapper needed)
|
||||
PORT_CTRL = 0xBF
|
||||
PORT_DATA = 0xBE
|
||||
|
||||
# VDP registers 0-10 for a plain 256x192 mode-4 background screen. Reg 1 starts
|
||||
# with the display OFF ($80); it is set to $C0 (display on) at the end.
|
||||
VDP_REGS = [0x04, 0x80, 0xFF, 0xFF, 0xFF, 0xFF, 0xFB, 0x00, 0x00, 0x00, 0xFF]
|
||||
|
||||
|
||||
def _code(reglen, patlen, ntlen) -> bytes:
|
||||
a = Asm(0x0000)
|
||||
a.di()
|
||||
a.im1()
|
||||
a.ld_sp(0xDFF0)
|
||||
|
||||
# --- VDP register init: write VDP_REGS[i] then $80|i ---
|
||||
a.ld_hl("REGDATA")
|
||||
a.ld_b(len(VDP_REGS))
|
||||
a.ld_c(0x00)
|
||||
a.label("rinit")
|
||||
a.ld_a_hl(); a.out_a(PORT_CTRL) # value
|
||||
a.ld_a_c(); a.or_n(0x80); a.out_a(PORT_CTRL) # $80 | reg
|
||||
a.inc_hl(); a.inc_c()
|
||||
a.djnz("rinit")
|
||||
|
||||
# --- upload patterns to VRAM $0000 ---
|
||||
a.xor_a(); a.out_a(PORT_CTRL)
|
||||
a.ld_a(0x40); a.out_a(PORT_CTRL) # $0000 | write
|
||||
a.ld_hl("PATDATA")
|
||||
a.ld_bc(patlen)
|
||||
a.label("ptile")
|
||||
a.ld_a_hl(); a.out_a(PORT_DATA)
|
||||
a.inc_hl(); a.dec_bc()
|
||||
a.ld_a_b(); a.or_c(); a.jp_nz("ptile")
|
||||
|
||||
# --- upload name table to VRAM $3800 ---
|
||||
a.xor_a(); a.out_a(PORT_CTRL)
|
||||
a.ld_a(0x78); a.out_a(PORT_CTRL) # $3800 | write ($38 | $40)
|
||||
a.ld_hl("NTDATA")
|
||||
a.ld_bc(ntlen)
|
||||
a.label("pnt")
|
||||
a.ld_a_hl(); a.out_a(PORT_DATA)
|
||||
a.inc_hl(); a.dec_bc()
|
||||
a.ld_a_b(); a.or_c(); a.jp_nz("pnt")
|
||||
|
||||
# --- upload palette to CRAM 0 ---
|
||||
a.xor_a(); a.out_a(PORT_CTRL)
|
||||
a.ld_a(0xC0); a.out_a(PORT_CTRL) # CRAM write
|
||||
a.ld_hl("PALDATA")
|
||||
a.ld_b(0x20) # 32 colours
|
||||
a.label("ppal")
|
||||
a.ld_a_hl(); a.out_a(PORT_DATA)
|
||||
a.inc_hl()
|
||||
a.djnz("ppal")
|
||||
|
||||
# --- disable sprites: write $D0 (list terminator) to SAT Y[0] at $3F00 ---
|
||||
a.xor_a(); a.out_a(PORT_CTRL)
|
||||
a.ld_a(0x7F); a.out_a(PORT_CTRL) # $3F00 | write
|
||||
a.ld_a(0xD0); a.out_a(PORT_DATA)
|
||||
|
||||
# --- display on (reg 1 = $C0) ---
|
||||
a.ld_a(0xC0); a.out_a(PORT_CTRL)
|
||||
a.ld_a(0x81); a.out_a(PORT_CTRL)
|
||||
a.label("hang")
|
||||
a.jp("hang")
|
||||
|
||||
# data labels live right after the code, in this order
|
||||
base = len(a.code)
|
||||
a.set_label("REGDATA", base)
|
||||
a.set_label("PATDATA", base + reglen)
|
||||
a.set_label("NTDATA", base + reglen + patlen)
|
||||
a.set_label("PALDATA", base + reglen + patlen + ntlen)
|
||||
return a.resolve()
|
||||
|
||||
|
||||
def build_rom(patterns: bytes, nametable: bytes, palette: bytes) -> bytes:
|
||||
code = _code(len(VDP_REGS), len(patterns), len(nametable))
|
||||
rom = bytearray(ROM_SIZE)
|
||||
blob = code + bytes(VDP_REGS) + bytes(patterns) + bytes(nametable) + bytes(palette)
|
||||
if len(blob) > 0x7FF0:
|
||||
raise ValueError("SMS image data overruns the 32KB cartridge")
|
||||
rom[0:len(blob)] = blob
|
||||
|
||||
# "TMR SEGA" header at $7FF0 (export region, 32KB) so the BIOS accepts it.
|
||||
rom[0x7FF0:0x7FF8] = b"TMR SEGA"
|
||||
rom[0x7FF8:0x7FFA] = bytes(2) # reserved
|
||||
chk = sum(rom[0:0x7FF0]) & 0xFFFF # checksum of $0000-$7FEF
|
||||
rom[0x7FFA] = chk & 0xFF
|
||||
rom[0x7FFB] = (chk >> 8) & 0xFF
|
||||
rom[0x7FFC:0x7FFF] = bytes(3) # product code / version
|
||||
rom[0x7FFF] = 0x4C # region 4 (export) | size $C (32K)
|
||||
return bytes(rom)
|
||||
|
||||
|
||||
def write_sms(rom: bytes, path: str) -> str:
|
||||
with open(path, "wb") as f:
|
||||
f.write(rom)
|
||||
return path
|
||||
77
lenser/sms/z80.py
Normal file
77
lenser/sms/z80.py
Normal file
|
|
@ -0,0 +1,77 @@
|
|||
"""Tiny Z80 machine-code emitter for the SMS VDP-setup viewer.
|
||||
|
||||
Only the handful of opcodes the viewer needs, with label support (two-pass: the
|
||||
data blocks are appended after the code, so their addresses depend on the code
|
||||
length). Same spirit as the project's other hand-rolled CPU emitters
|
||||
(ti99/tms9900.py, coco/mc6809.py, intv/cp1610.py).
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
|
||||
class Asm:
|
||||
def __init__(self, org: int = 0x0000):
|
||||
self.org = org
|
||||
self.code = bytearray()
|
||||
self.labels: dict[str, int] = {}
|
||||
self.fixups: list[tuple[int, str, str]] = [] # (pos, label, kind)
|
||||
|
||||
def _b(self, *bs):
|
||||
for b in bs:
|
||||
self.code.append(b & 0xFF)
|
||||
|
||||
def label(self, name):
|
||||
self.labels[name] = self.org + len(self.code)
|
||||
|
||||
def set_label(self, name, addr):
|
||||
self.labels[name] = addr
|
||||
|
||||
def _w(self, v):
|
||||
"""emit a 16-bit little-endian operand; v is int or a label name."""
|
||||
if isinstance(v, str):
|
||||
self.fixups.append((len(self.code), v, "abs"))
|
||||
self._b(0, 0)
|
||||
else:
|
||||
self._b(v & 0xFF, (v >> 8) & 0xFF)
|
||||
|
||||
def _rel(self, label):
|
||||
self.fixups.append((len(self.code), label, "rel"))
|
||||
self._b(0)
|
||||
|
||||
# --- instructions ---
|
||||
def nop(self): self._b(0x00)
|
||||
def di(self): self._b(0xF3)
|
||||
def im1(self): self._b(0xED, 0x56)
|
||||
def halt(self): self._b(0x76)
|
||||
def xor_a(self): self._b(0xAF)
|
||||
def ld_sp(self, n): self._b(0x31); self._w(n)
|
||||
def ld_a(self, n): self._b(0x3E, n)
|
||||
def ld_b(self, n): self._b(0x06, n)
|
||||
def ld_c(self, n): self._b(0x0E, n)
|
||||
def ld_hl(self, n): self._b(0x21); self._w(n)
|
||||
def ld_bc(self, n): self._b(0x01); self._w(n)
|
||||
def ld_a_hl(self): self._b(0x7E) # ld a,(hl)
|
||||
def ld_a_b(self): self._b(0x78)
|
||||
def ld_a_c(self): self._b(0x79)
|
||||
def or_n(self, n): self._b(0xF6, n)
|
||||
def or_c(self): self._b(0xB1)
|
||||
def inc_hl(self): self._b(0x23)
|
||||
def inc_c(self): self._b(0x0C)
|
||||
def dec_bc(self): self._b(0x0B)
|
||||
def out_a(self, port): self._b(0xD3, port) # out (port),a
|
||||
def djnz(self, label): self._b(0x10); self._rel(label)
|
||||
def jp(self, label): self._b(0xC3); self._w(label)
|
||||
def jp_nz(self, label): self._b(0xC2); self._w(label)
|
||||
|
||||
def resolve(self) -> bytes:
|
||||
out = bytearray(self.code)
|
||||
for pos, label, kind in self.fixups:
|
||||
target = self.labels[label]
|
||||
if kind == "abs":
|
||||
out[pos] = target & 0xFF
|
||||
out[pos + 1] = (target >> 8) & 0xFF
|
||||
else: # rel: from the byte AFTER the operand
|
||||
disp = target - (self.org + pos + 1)
|
||||
if not -128 <= disp <= 127:
|
||||
raise ValueError(f"rel jump out of range to {label}")
|
||||
out[pos] = disp & 0xFF
|
||||
return bytes(out)
|
||||
Loading…
Add table
Add a link
Reference in a new issue