Skip to content

Commit

Permalink
build: convert nimscript into compiled nim programs
Browse files Browse the repository at this point in the history
  • Loading branch information
ZoomTen committed Jul 9, 2024
1 parent 305dc37 commit 7cce459
Show file tree
Hide file tree
Showing 6 changed files with 220 additions and 96 deletions.
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,5 @@ _build/
*.map
*.noi
*.sym
tools/compile
tools/link
26 changes: 21 additions & 5 deletions config.nims
Original file line number Diff line number Diff line change
Expand Up @@ -22,18 +22,28 @@ let

# Setup scripts
#-------------------------------------#
proc setupGbdk* () =

proc precompileTools() =
let tools = ["compile", "link"]

for toolName in tools:
if findExe("tools" / toolName) == "":
echo "make '" & toolName & "' wrapper..."
selfExec([
"c", "-d:release", "--hints:off",
thisDir() / "tools" / toolName & ".nim"
].join(" "))

proc setupGbdk() =
# set c compiler as ""icc"" but is actually sdcc
switch "cc", "icc"

# abuse the c compiler options to use a nimscript
# for compiling, linking and finalization
# only works on unix-like environments unfortunately
# can't trick it into using the current nim exe :(
put "icc.exe", thisDir()/"tools"/"compiler.nims"
put "icc.exe", thisDir()/"tools"/"compile"
put "icc.options.always", ""

put "icc.linkerexe", thisDir()/"tools"/"link.nims"
put "icc.linkerexe", thisDir()/"tools"/"link"
put "icc.options.linker", ""

# basic nim compiler options
Expand Down Expand Up @@ -81,6 +91,7 @@ if projectPath() == thisDir()/mainFile:
# Entry points
#-------------------------------------#
task build, "Build a Game Boy ROM":
precompileTools()
let
args = commandLineParams()[1..^1].join(" ")
selfExec([
Expand Down Expand Up @@ -109,4 +120,9 @@ task cleanDist, "Clean up this directory's residual files":
for ext in [".ihx", ".map", ".noi"]:
rmFile(romName & ext)
echo("removed $#$#" % [romName, ext])

for ext in ["", ".exe"]:
for toolProg in ["tools"/"compile", "tools"/"link"]:
rmFile(toolProg & ext)
echo("removed $#$#" % [toolProg, ext])
#-------------------------------------#
16 changes: 4 additions & 12 deletions tools/compiler.nims → tools/compile.nim
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
#!/usr/bin/env -S nim e --hints:off

## Mimics icc for compiling. Its purpose is to discern between
## C sources and ASM sources and makes it somewhat manageable.
##
Expand All @@ -16,14 +14,8 @@ import ./helpers
import ../romConfig # bleh

when isMainModule:
# check for gbdk root as in ../config.nims
let
gbdkRoot = getEnv("GBDK_ROOT")
when not defined(nimsuggest):
assert gbdkRoot != "", "Please set the GBDK_ROOT environment variable."

# parse the params minus "nim" "e" "--hints:off"
var inputs = commandLineParams()[3..^1].join(" ").paramsToSdldInput()
let gbdkRoot = getGbdkRoot()
var inputs = commandLineParams().join(" ").paramsToSdldInput()

# I would hope this was invoked as 1 source file = 1 object file
let
Expand All @@ -40,7 +32,7 @@ when isMainModule:
"-c", # compile only
# basic includes
"-I" & gbdkRoot / "include", # gbdk libraries
"-I" & thisDir().parentDir() / "include", # our stuff and our nimbase.h
"-I" & getCurrentDir() / "include", # our stuff and our nimbase.h
# target architecture
"-msm83",
"-D" & "__TARGET_gb",
Expand Down Expand Up @@ -68,4 +60,4 @@ when isMainModule:
]

else: raise newException(Exception, "unknown format")
).join(" "), false) # bypass warnings
).join(" ")) # bypass warnings
23 changes: 18 additions & 5 deletions tools/helpers.nim
Original file line number Diff line number Diff line change
@@ -1,15 +1,27 @@
## Helper functions, not compiled into anything but
## used by the other two files.

import std/os
import std/parseopt
import std/strutils
import std/osproc

type
SdldInput* = object
outputFile*: string
objFiles*: seq[string]

proc getGbdkRoot* (): string =
let
gbdkRoot = getEnv("GBDK_ROOT")
when not defined(nimsuggest):
assert gbdkRoot != "", "Please set the GBDK_ROOT environment variable."
return gbdkRoot

proc paramsToSdldInput* (cmdline: string): SdldInput =
var
opts = cmdline.initOptParser()
forOutput = false # accomodate short option separated by space
result: SdldInput
while true:
opts.next()
case opts.kind
Expand All @@ -28,13 +40,14 @@ proc paramsToSdldInput* (cmdline: string): SdldInput =
return result

proc execWithEcho* (command: string, exceptOnError: bool = true) =
#echo command
let (outStr, exitCode) = gorgeEx(command)
echo command.strip()
let (outStr, exitCode) = execCmdEx(command)
let outStrDisp = outStr.strip()
if exceptOnError:
if exitCode != 0:
raise newException(Exception, outStr)
else:
echo outStr
if outStrDisp != "": echo outStrDisp
else:
echo outStr
if outStrDisp != "": echo outStrDisp

175 changes: 175 additions & 0 deletions tools/link.nim
Original file line number Diff line number Diff line change
@@ -0,0 +1,175 @@
## Mimics icc for the linking and everything
## Calls sdldgb directly.
##
## Not only is this basically a hack off Nim's default linker
## assumptions, but putting this inside config.nims will make it
## run *as* Nim is compiling, which isn't what I want

import os
import ./helpers

import ../romConfig # bleh

import std/streams
import std/strutils
import std/math

########################################################################

type
RecordKind = enum
Data = 0
Eof
ExSegAddr
StSegAddr
ExLinAddr
StLinAddr
Record = object
kind: RecordKind
address: uint16
data: seq[byte]
IhxObject = object
records: seq[Record]

proc toRecord (s: string): Record =
var ss = s.newStringStream()
if ss.readChar() != ':':
raise newException(CatchableError, "starting byte must be ':'")
let recLength = fromHex[uint8](ss.readStr(2))
result.data.setLen(recLength)
result.address = fromHex[uint16](ss.readStr(4))
result.kind = cast[RecordKind](fromHex[int](ss.readStr(2)))
for i in 0'u8..<recLength:
result.data[i] = fromHex[byte](ss.readStr(2))
# TODO: validate checksum?

proc makeBin(ihxFileName: string): seq[byte] =
var
s = openFileStream(ihxFileName, fmRead)
o: string
i: IhxObject
highestAddr: uint16 = 0'u16
while s.readLine(o):
i.records.add(o.toRecord())
if (
let someHighAddr =
uint16(
int(i.records[^1].address) + i.records[^1].data.len()
)
someHighAddr > highestAddr
):
highestAddr = someHighAddr

var canvas = newSeq[byte](highestAddr)
for record in i.records:
for byteIndex in 0..<record.data.len():
canvas[record.address + uint16(byteIndex)] =
record.data[byteIndex]
return canvas

proc ihx2bin(ihxFileName, binFileName, gameName: string): void =
var
canvas = ihxFileName.makeBin()
st = binFileName.openFileStream(fmWrite)
canvas.setLen(
ceilDiv(canvas.len, 0x4000) * 0x4000
)

# write the game title in the header
if gameName.len > 16:
raise newException(CatchableError, "game name must be <= 16 characters")

# I want to use canvas[a..b] = string :(
for i in 0..<0x10:
if i > (gameName.len - 1):
break
canvas[0x134 + i] = byte(gameName[i])

# fix both checksums
# header
var headerCheck = 0'u8
for i in 0x134..0x14c:
headerCheck += canvas[i]
canvas[0x14d] = headerCheck

# global
var globalCheck = 0'u16
for i in 0..<canvas.len:
if i in 0x14e..0x14f:
continue
globalCheck += uint16(canvas[i])
canvas[0x14e] = uint8((globalCheck shr 8) and 0xff)
canvas[0x14f] = uint8(globalCheck and 0xff)


# write the actual file
st.writeData(canvas[0].addr, canvas.len)

proc noi2sym(noiFileName, symFileName: string): void =
var
o: string
syms: seq[string]
s = openFileStream(noiFileName, fmRead)
while s.readLine(o):
if o.startsWith("DEF "):
let n = o.split(' ')
assert n.len == 3
# TODO: convert n[2] 0x???? to GameBoy ROM format
syms.add("$#:$# $#" % ["00", n[2], n[1]])
var m = openFileStream(symFileName, fmWrite)
for sym in syms:
m.writeLine(sym)

########################################################################

when isMainModule:
let gbdkRoot = getGbdkRoot()
var inputs = commandLineParams().join(" ").paramsToSdldInput()

let
(outfDir, outfName, outfExt) = inputs.outputFile.splitFile()

# first link everything into an .ihx file
# going with whatever LCC has as the default
execWithEcho((@[
gbdkRoot / "bin" / "sdldgb",
# "-n", # silent
"-i", # output to IHX
"-m", # generate map output
"-j", # generate NoICE debug file
"-u", # update all the listing files to reflect actual locations
# define globals
"-g _shadow_OAM=0x" & virtualSpritesStart.toHex(4),
"-g .STACK=0x" & stackStart.toHex(4),
"-g .refresh_OAM=0x" & oamHramCodeStart.toHex(4),
# define base addrs
"-b _DATA=0x" & dataStart.toHex(4),
"-b _CODE=0x" & codeStart.toHex(4),
# add libraries
"-k " & gbdkRoot/"lib"/"sm83", "-l sm83.lib",
# output to:
outfDir / outfName & ".ihx",
] &
# order object files as Nim orders them
inputs.objFiles
).join(" "))

# turn it into a GB ROM
when true:
(outfDir / outfName & ".ihx").ihx2bin(outfDir / outfName & outfExt, romTitle)
# Autosizing already handled
# Figure out what makebin's -Z option does
(outfDir / outfName & ".noi").noi2sym(outfDir / outfName & ".sym")
else:
execWithEcho([
gbdkRoot / "bin" / "makebin",
"-yN", # skip injecting nintendo logo
"-Z", # specify Game Boy binary
"-yS", # Convert NoICE symfile to NO$GMB/BGB/standard symfile
"-yo A", # Autosize ROM
"-yn " & romTitle.quoteShell(), # give the ROM a cartname
# which file?
outfDir / outfName & ".ihx",
# where to?
outfDir / outfName & outfExt
].join(" "))
74 changes: 0 additions & 74 deletions tools/link.nims

This file was deleted.

0 comments on commit 7cce459

Please sign in to comment.