-
Notifications
You must be signed in to change notification settings - Fork 0
/
mkloadob.html
1124 lines (1103 loc) · 49.3 KB
/
mkloadob.html
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
<html>
<head>
<meta http-equiv="Content-Language" content="en-us">
<meta name="GENERATOR" content="Microsoft FrontPage 4.0">
<meta name="ProgId" content="FrontPage.Editor.Document">
<meta http-equiv="Content-Type" content="text/html; charset=windows-1252">
<title>MKLOADOB (the Echidna Data Linker)</title>
<style>
<!--
.elist1 { background-color: #E3E3FF; vertical-align:top }
.elist2 { background-color: #F0F0FF; vertical-align:top }
pre { margin-bottom: 0 }
h2 { border-top-style: solid; padding-top: 10 }
-->
</style>
</head>
<body>
<h1>MKLOADOB (the Echidna Data Linker)</h1>
<hr>
<ul>
<li><a href="#why"><span lang="ja">W</span></a><span lang="ja"><a href="#why">hy,
What, Where</a></span></li>
<li><a href="#introduction"><span lang="ja">I</span>ntroductio<span lang="ja">n
and example</span></a></li>
<li><a href="#running"><span lang="ja">R</span>unnin<span lang="ja">g the
linker</span></a></li>
<li><a href="#loading"><span lang="ja">L</span>oadin<span lang="ja">g your
data</span></a></li>
<li><a href="#download"><span lang="ja">D</span>ownloa<span lang="ja">d</span></a></li>
<li><a href="#format"><span lang="ja">Data Linker input file f</span>ormat</a></li>
<li><a href="#numberformat"><span lang="ja">Nu</span>m<span lang="ja">eric Fo</span>rma<span lang="ja">ts</span></a></li>
<li><a href="#arguments"><span lang="ja">Command line a</span>rguments</a></li>
<li><a href="#macros"><span lang="ja">M</span>acro<span lang="ja"> Language</span></a></li>
<li><a href="#tips"><span lang="ja">T</span>ip<span lang="ja">s and
Suggestions</span></a></li>
<li><a href="#cyd"><span lang="ja">Chunkifying your data</span></a></li>
</ul>
<p><a name="why"></a>Back in the early 90s as soon as CD based game systems came out it became
clear that one of the big problems with CD based games was they took forever to
load! Those problems were mostly solved by several companies back in the
early 90s but it seems like almost no one noticed because we still get crap like
Crash Bandicoot: Wrath of Cortex with atrocious load times. Pick a level,
wait 90 seconds, take a few steps, die, wait 90 seconds for the hub level to
load so you can immediately jump back into the level and have to wait another 90
seconds!!</p>
<p>How do you get around these problems? The biggest step is realizing
that A CONSOLE IS NOT A PC!!!! Burn that into your brain!!!</p>
<p>It's not uncommon for PC games to just put each texture, each model, each
sound in a separate file. When you install the game all of them get copied
to your hard drive and when you start the game they load them up, one at a time.
The only thing that makes this possibly acceptable on a PC is you are loading
them off the hard drive or a 40x CD/DVD drive with disk caching built into the
OS. Good consoles don't have an OS and to keep costs down they don't have
40x CD/DVD drives.</p>
<p>The other excuse is nobody knows what kind of PC the game will run on.
Will it be a 512meg Pentium 4 with 128Meg of Video RAM or a 300mhz Celeron and a
4Meg RIVA 128 card? In that case sometimes there might be different
versions of each model, texture, sound etc. Consoles don't have this
problem. There is only one type. If you are making a PS2 game you
know exactly what hardware the user will have.</p>
<p>Basically here is what you need to do to get your load times to be as fast as
possible.</p>
<ul>
<li>Load all your data from ONE FILE!!!</li>
</ul>
<blockquote>
<p>There are two steps to this. One, for a single stage/level, attempt to load
all the data for that stage from one file directly into memory. No
loading one model from one file and a different model from a different file.
Even if that means putting some of the same models, textures or sounds on the
DVD more than once, do it! You've got a friggin DVD worth of space.
Use it!</p>
<blockquote>
<p>Note! I do not mean a WAD files, PAK files or CAB file likes are used in
many PC games. Although those are better than separate files they will
not speed up your load times much at all because you are still treating them
as separate files.</p>
</blockquote>
<p>The second step is putting all the files in your game (the one file for
each stage) all together into one BIG file that contains all the data for your
entire game. This is not a hard thing to do and it will remove even more
overhead for your game.</p>
</blockquote>
<ul>
<li>Load your data in chucks no smaller than 1 DVD hardware sector. In
other words, if on your platform a DVD hardware sector holds 2048 bytes of data
then ALWAYS LOAD A MULTIPLE OF 2048 bytes. Never load less even if you
have to pad the file.</li>
</ul>
<blockquote>
<p>The reason is on some systems data can be DMAed directly into memory but
this is only true if you are DMAing a whole sector. Anything less than a
sector and it first has to be read into some buffer in the OS and then copied
to your memory. That also means you cannot read a few bytes from the
file and then start reading multiples of sector size chucks of data. You
have to start right from the beginning going at full speed.</p>
</blockquote>
<ul>
<li>Finally, load your data in as close to an image of memory as possible.
You should NOT have to parse your data while your are loading it or after.
With very few exceptions you should NOT have to copy things around. I've
seen many schemes that make some file format where there are "chunks". A
chunk is usually some kind of ID followed by the length of the chunk after
which follows the next chunk. That is NOT a good format for a game.
That's a great format for a tool or an application but not for a game because
parsing takes time, code and memory. More time, more code and more
memory than not parsing. In a game, anything
which takes more time or memory or code is BAD. Don't do it!</li>
</ul>
<p>It's this last step that I'm here to help you with. In the early 1990s
my friends and I wrote a tool to deal with this. Since about 1997 we have
made it freely available but without docs it's been unlikely that anybody has used it let
alone even found it, until now!</p>
<h2><a name="introduction"></a>The Echidna Data Linker</h2>
<p>It's what we call a Data Linker because it takes a bunch of data an links it
together. It's called MKLOADOB. Lame name I know but hey, it's old,
starting back from the days of DOS. If you are curious, MKLOADOB stands for MaKe
LOAD OBject.</p>
<p>It takes a bunch of spec files of the form below and generates a hierarchical
binary file with pointers.</p>
<h3>Example:</h3>
<p>Here's a short example. Let's say you want to have level structure that
is an<span lang="ja"> array of</span> instances where an instance is a model pointer, a position and a y
rotation. Here's the C/C++ structure for what I'm talking about</p>
<blockquote>
<pre>// level.h
//
struct Instance
{
float transx;
float transy;
float transz;
float rotationy;
Model* pModel;
};
struct Level
{
int numInstances;
Instance* pInstances;
};</pre>
</blockquote>
<p>Now lets make a small level with a ground model, a house model and 2 trees of
the same model using the data linker</p>
<blockquote>
<pre>// linker file
//
[start]
long=4 ; number of models
pntr=modeltable
[modeltable]
float=0.0 0.0 0.0 ; translation for ground
float=0.0 ; rotation for ground
file=ground.3d ; model file for ground
float=5.0 0.0 3.0 ; translation for house
float=45.0 ; rotation for house
file=house.3d ; model file for house
float=-4.0 0.0 -2.0 ; translation for tree1
float=30.0 ; rotation for tree1
file=tree.3d ; model file for tree
float=-2.0 0.0 -4.0 ; translation for tree2
float=225.0 ; rotation for tree2
file=tree.3d ; model file for tree</pre>
</blockquote>
<p>You pass this file to the data linker and you'll get a binary file that you
can load directly into memory and with only a few lines of code all your
pointers will already be pointing at your data.</p>
<p>Above notice the third line that says "pntr=". This line generates a
pointer to a section that matches the name after the '='. You can have as
many pointers as you need/want. They can be nested to any level and they
can be circular.
Also<span lang="ja">,</span> notice that the file tree.3d is referenced twice. In this case the
file tree.3d would be stored only once inside the binary file that the data linker creates
but there would be two pointers pointing to that data.</p>
<p><span lang="ja">For a real game, most of the time we would not write data
linker files by hand. We would make our tools generate data linker files
and then let the data linker pull it all together. There's n</span>o need
to hand load and hand parse stuff<span lang="ja"> which is another huge
advantage. Most games that use individual files or chunky formats end up
reading a few bytes at a time and *parsing* the data, reading the size of this
or that, allocating bits of memory at time etc.. That's a lot of work.
Many loaders are hundreds of lines long. Using the a data linker removes
all that code. Less code = faster development, faster loading, more
memory, less bugs.</span></p>
<h2><a name="running"></a>Running the Data Linker</h2>
<p>A typical command line might look like this</p>
<blockquote>
<pre>mkloadob mylevel.lbi -duperr -sectorsize 2048 -chunksize $2800000
-nopad -nosort -fixupmode 2 -padsize 4 linkerfile.dlk</pre>
</blockquote>
<p>Those options say:</p>
<table border="0" cellpadding="5" cellspacing="0" style="border-collapse: collapse" bordercolor="#111111">
<tr>
<td valign="top" bgcolor="#E3E3FF" nowrap>mylevel.lbi</td>
<td valign="top" bgcolor="#E3E3FF">the name of the file we are going to
create</td>
</tr>
<tr>
<td valign="top" bgcolor="#F0F0FF" nowrap>-duperr</td>
<td valign="top" bgcolor="#F0F0FF">if two sections have the same name print
an error</td>
</tr>
<tr>
<td valign="top" bgcolor="#E3E3FF" nowrap>-sectorsize 2048</td>
<td valign="top" bgcolor="#E3E3FF">one sector of the disk is 2048 bytes so
our filesize will be padded to a multiple of 2048 bytes</td>
</tr>
<tr>
<td valign="top" bgcolor="#F0F0FF" nowrap>-chunksize $2800000</td>
<td valign="top" bgcolor="#F0F0FF">chunks are probably not something you will
use. In this case we just want one big chunk so we set the chunksize
to some giant size</td>
</tr>
<tr>
<td valign="top" bgcolor="#E3E3FF" nowrap>-nopad</td>
<td valign="top" bgcolor="#E3E3FF">normally all chucks would get filled out
to be chunksize big but in this case we don't want that since there is only
one chunk.</td>
</tr>
<tr>
<td valign="top" bgcolor="#F0F0FF" nowrap>-nosort</td>
<td valign="top" bgcolor="#F0F0FF">don't sort any binary included files.
Normally they get sorted so that they can be squeezed into the various
chunks but since we are only going to have one chunk there is no point.</td>
</tr>
<tr>
<td valign="top" bgcolor="#E3E3FF" nowrap>-fixupmode 2</td>
<td valign="top" bgcolor="#E3E3FF">put the pointer fixup table at the end of
the file. Because it's at the end, once the pointers have been fixed
up you can free that part of memory if you want to</td>
</tr>
<tr>
<td valign="top" bgcolor="#F0F0FF" nowrap>-padsize 4</td>
<td valign="top" bgcolor="#F0F0FF">is the alignment for sections. All
sections and files will start at a 4 byte boundary by default</td>
</tr>
</table>
<h2><a name="loading"></a>Loading Your Data</h2>
<p>In the case of a one chunk file, loading your data is pretty straight
forward. Here's some sample code:</p>
<blockquote>
<pre>#include "link.h"</pre>
<pre>struct Level* pLevel; // the level structure from above
pLevel = LINK_QuickLoadBlockFile ("mylevel.lbi");</pre>
</blockquote>
<p>As you can tell that was extremely difficult! :-p Your level is now
loaded and all your pointers are correct.</p>
<p>The example loading and pointer fixup files are in
<a href="https://github.com/greggman/elibs/tree/master/elibs/tools/mkloadob/">
the same folder as mkloadob</a> in the CVS repository. See
<a href="https://github.com/greggman/elibs/tree/master/elibs/tools/mkloadob/quickload.cpp">
Quickload.cpp</a>,
<a href="https://github.com/greggman/elibs/tree/master/elibs/tools/mkloadob/quickload.h">
Quickload.h</a>,
<a href="https://github.com/greggman/elibs/tree/master/elibs/tools/mkloadob/link.cpp">
Link.cpp</a> and
<a href="https://github.com/greggman/elibs/tree/master/elibs/tools/mkloadob/link.h">
Link.h</a></p>
<h2><span lang="ja"><a name="download"></a>Downloading</span></h2>
<p><span lang="ja">The Echidna Data Linker is one of the tools in the Echidna
Libraries which can be <a href="http://github.com/greggman/elibs">accessed through
github.com here</a>.</span></p>
<h2><a name="format"></a>Data Linker File Format</h2>
<p>The format of the data linker files is as follows</p>
<h3>Preprocessor commands:</h3>
<table border="0" cellpadding="5" cellspacing="0" style="border-collapse: collapse">
<tr>
<td class="elist1">#include "filename"</td>
<td class="elist1">includes another linker file</td>
</tr>
<tr>
<td class="elist2">#define var value</td>
<td class="elist2">defines a var that can be referenced as %var%</td>
</tr>
<tr>
<td class="elist2">#if value</td>
<td class="elist2">0 = ignore lines until #elif, #else, or #endif</td>
</tr>
<tr>
<td class="elist2">#elif value</td>
<td class="elist2">0 = ignore lines until #elif, #else, or #endif</td>
</tr>
<tr>
<td class="elist2">#else</td>
<td class="elist2">ignore lines if a previous #if, #elif area was not
ignored</td>
</tr>
<tr>
<td class="elist2">#endif</td>
<td class="elist2">marks the end of the current #if group</td>
</tr>
<tr>
<td class="elist2">#error msg</td>
<td class="elist2">generates an error</td>
</tr>
</table>
<h3>Commands:</h3>
<p>IMPORTANT!!!</p>
<p>data fields are not automatically aligned. In other words</p>
<blockquote>
<pre>[mysection]
byte=1
long=2</pre>
</blockquote>
<p>Will result in a long starting at a 1 byte offset from the start of mysection.
The reason is this allows you to use the data linker to make unaligned data. If
you want aligned data you need to use the align= option<span lang="ja"> as in</span></p>
<blockquote>
<pre><span lang="ja">[mysection]
byte=1
align=4
long=2</span></pre>
</blockquote>
<table border="0" cellpadding="5" cellspacing="0" style="border-collapse: collapse">
<tr>
<td class="elist1">[secname]</td>
<td class="elist1">start a section<p>You can set the alignment for
a section as follows</p>
<blockquote>
<pre>[mysection,align=16]</pre>
</blockquote>
<p>Mysection will start at a 16 byte boundary</p>
<p>NOTE: the alignment is relative to the address of the particular block
this section ends up being placed in. The alignment of block is up to the
loader in the program you are using this data.</td>
</tr>
<tr>
<td class="elist2">file=</td>
<td class="elist2">pointer to a file : quotes are optional<p>You
can also optionally specify an alignment for the file like this</p>
<blockquote>
<pre>file=myfile.bin,align=16</pre>
</blockquote>
<p>alignment is relative to the address of the particular block
this files ends up being placed in. The alignment of block is up to the
loader in the program you are using this data.</p>
<p>You can also optionally allow a file to not exist like this</p>
<blockquote>
<pre>file=myfile.bin,nullok=true</pre>
</blockquote>
<p>In this case if the file does not exist a NULL pointer will be
inserted. Normally the linker would print an error.</p>
<p>To understand <span lang="ja">the </span>file<span lang="ja">= command</span>, the long hand way of writing this would be as follows</p>
<blockquote>
<pre>...
pntr=myfile.bin.section
...
[myfile.bin.section]
binc=myfile.bin</pre>
</blockquote>
</td>
</tr>
<tr>
<td class="elist1">data=</td>
<td class="elist1">pointer to a file (same as file=) : quotes are
optional</td>
</tr>
<tr>
<td class="elist2">load=</td>
<td class="elist2">pointer to a file loaded at runtime : quotes
are optional
<p>note: this will actually start as a pointer to a filename with with the
POSITIONFLAG_ISFILE set to tell your loader that you need to load the file
</p>
<p>since mkloadob excepts variables both as environment vars and as #define
var value, the expected usage of load= is as follows</p>
<blockquote>
<pre>#define objectload=load
;#define objectload=file
#define weaponsload=load
;#define weaponsload=file
%objectload%="myobject1.ob"
%objectload%="myobject2.ob"
%weaponload%="myweapon1.foo"
%weaponload%="myweapon2.foo"</pre>
<p>this way you can comment turn on/off individual files or file types
throughout.</p>
</blockquote>
</td>
</tr>
<tr>
<td class="elist1">pntr=</td>
<td class="elist1">pointer to another section<p>You can also optionally
allow the section to not exist like this</p>
<blockquote>
<pre>pntr=mysection,nullok=true</pre>
</blockquote>
<p>In this case if the section does not exist a NULL pointer will be
inserted. Normally the linker would print an error.</p>
</td>
</tr>
<tr>
<td class="elist2">level=</td>
<td class="elist2">pointer to another section (same as <span lang="ja">pntr</span>=)</td>
</tr>
<tr>
<td class="elist1">long=</td>
<td class="elist1">longs (4 bytes each)</td>
</tr>
<tr>
<td class="elist2">int32=</td>
<td class="elist2">longs (4 bytes each)</td>
</tr>
<tr>
<td class="elist1">uint32=</td>
<td class="elist1">longs (4 bytes each)</td>
</tr>
<tr>
<td class="elist2">word=</td>
<td class="elist2">words (2 bytes each)</td>
</tr>
<tr>
<td class="elist1">short=</td>
<td class="elist1">words (2 bytes each)</td>
</tr>
<tr>
<td class="elist2">int16=</td>
<td class="elist2">words (2 bytes each)</td>
</tr>
<tr>
<td class="elist1">uint16=</td>
<td class="elist1">words (2 bytes each)</td>
</tr>
<tr>
<td class="elist2">byte=</td>
<td class="elist2">bytes (1 byte each)</td>
</tr>
<tr>
<td class="elist1">char=</td>
<td class="elist1">bytes (1 byte each)</td>
</tr>
<tr>
<td class="elist2">int8=</td>
<td class="elist2">bytes (1 byte each)</td>
</tr>
<tr>
<td class="elist1">uint8=</td>
<td class="elist1">bytes (1 byte each)</td>
</tr>
<tr>
<td class="elist2">float=</td>
<td class="elist2">floats (4 bytes each)</td>
</tr>
<tr>
<td class="elist1">string=</td>
<td class="elist1">string, note: NO NULL IS INSERTED!!!<table border="0" cellpadding="0" cellspacing="0" style="border-collapse: collapse" bordercolor="#111111" width="100%">
<tr>
<td class="elist1">
<pre>string=ABC</pre>
</td>
<td class="elist1">inserts ABC</td>
</tr>
<tr>
<td class="elist1">
<pre>string="ABC"</pre>
</td>
<td class="elist1">inserts "ABC" (including the quotes!!!)</td>
</tr>
</table>
</td>
</tr>
<tr>
<td class="elist2">binc= </td>
<td class="elist2">binary file to include here, quotes are
optional</td>
</tr>
<tr>
<td class="elist1">align=</td>
<td class="elist1">boundary to align to (no arg or 0 = default)<p>
NOTE: THIS ALIGNS BASED ON THE START OF THE SECTION<span lang="ja">,</span> NOT MEMORY.</p>
<p>In other words, if you have section like this</p>
<blockquote>
<pre>[mysection]
byte=1
align=4
long=$12345678</pre>
</blockquote>
<p>You will get 3 bytes of padding after the first byte BUT the section will
be placed based on the -PADSIZE option. If -PADSIZE is set to a non multiple
of 4 in the example above it's possible the section will not start at a 4
byte boundary and therefore neither will the long</td>
</tr>
<tr>
<td class="elist2">pad=</td>
<td class="elist2">pad with N bytes<blockquote>
<pre>pad=24</pre>
</blockquote>
<p>insert 24 bytes (value 0)</td>
</tr>
<tr>
<td class="elist1">path= </td>
<td class="elist1">set the path for loading binary files. Files
encountered after this line will be loaded from here<p> NOTE: Files are
parsed from the TOP section to each connecting section so for example</p>
<blockquote>
<pre>[start]
pntr=section1
file=file3.bin ; load dir\subdir\file2.bin
[section1]
path=dir\subdir
file=file1.bin ; loads dir\subdir\file1.bin</pre>
</blockquote>
<p><span lang="ja">Also note that by default, files are loaded relative to
the file they are referenced from first. In other words if a file=
statement is found in c:\mydir\myfile.dlink the file will be looked for in
c:\mydir </span>
</td>
</tr>
<tr>
<td class="elist2">insert=</td>
<td class="elist2">inserts another section here. Not a
pointer, the actual section so for example this:<blockquote>
<pre>[start]
byte=1 2 3
insert=othersection
byte=7 8 9
[othersection]
byte=4
byte=5 6</pre>
</blockquote>
<p>is the same as this</p>
<blockquote>
<pre>[start]
byte=1 2 3
byte=4
byte=5 6
byte=7 8 9</pre>
</blockquote>
</td>
</tr>
</table>
<h2><a name="numberformat"></a>Numeric Format</h2>
<p>You can specify integer based numbers as follows</p>
<blockquote>
<table border="0" cellpadding="4" cellspacing="0" style="border-collapse: collapse" bordercolor="#111111">
<tr>
<td class="elist1">
<pre>1234</pre>
</td>
<td class="elist1">= decimal 1234</td>
</tr>
<tr>
<td class="elist2">
<pre>$1234</pre>
</td>
<td class="elist2">= hexadecimal 0x1234</td>
</tr>
<tr>
<td class="elist1">
<pre>0x1234</pre>
</td>
<td class="elist1">= hexadecimal 0x1234</td>
</tr>
<tr>
<td class="elist2">
<pre>1234h</pre>
</td>
<td class="elist2">= hexadecimal 0x1234</td>
</tr>
<tr>
<td class="elist1">
<pre>!12.34</pre>
</td>
<td class="elist1">= fixed point 16.16 fraction</td>
</tr>
</table>
</blockquote>
<h2><a name="arguments"></a>Command Line Arguments</h2>
<p>Usage: MKLOADOB OUTFILE SPECFILES [switches...]</p>
<table border="0" cellpadding="5" cellspacing="0" style="border-collapse: collapse">
<tr>
<td class="elist1" nowrap>OUTFILE</td>
<td class="elist1">Output filename</td>
</tr>
<tr>
<td class="elist2" nowrap>SPECFILES</td>
<td class="elist2">Linker files. Note it says FILES. As in
PLURAL. You can use include statements and/or you can specify as many
files as you need right here. Files are parsed from the [start]
section so the order sections appear in the files and the order
<span lang="ja">they are</span> includ<span lang="ja">ed is</span> n<span lang="ja">ot</span> relevant (<a href="#relevant">*</a>)</td>
</tr>
<tr>
<td class="elist1" nowrap>-VERBOSE</td>
<td class="elist1">Verbose (print too much info)</td>
</tr>
<tr>
<td class="elist2" nowrap>-NOOUTPUT</td>
<td class="elist2">Don't write output file</td>
</tr>
<tr>
<td class="elist1" nowrap>-DUPERR</td>
<td class="elist1">To sections with the same name cause an error (default,
ignore duplicates)</td>
</tr>
<tr>
<td class="elist2" nowrap>-NOPACK</td>
<td class="elist2">Don't Pack. Don't check the contents of each binary
file to see if it match another binary file</td>
</tr>
<tr>
<td class="elist1" nowrap>-BIGENDIAN</td>
<td class="elist1">Big Endian, default is little endian.</td>
</tr>
<tr>
<td class="elist2" nowrap>-PADSIZE <bytes></td>
<td class="elist2">Padsize Size (Def. 4). This is the default alignment for
the start of sections</td>
</tr>
<tr>
<td class="elist1" nowrap>-CHUNKSIZE <bytes></td>
<td class="elist1">Chunk Size (Def. 2048). This is the size of one
chuck. See <a href="#cyd">Chunkifying your data</a>. Most likely you'll want to set
this to something like $<span lang="ja">7F0</span>00000</td>
</tr>
<tr>
<td class="elist2" nowrap>-SECTORSIZE <bytes></td>
<td class="elist2">Hardware Sector Size (Def. 1). If you set this to
any<span lang="ja"> </span>size larger then 1 your file will be padded to be a multiple of this
number.</td>
</tr>
<tr>
<td class="elist1" nowrap>-FIXUPMODE <mode></td>
<td class="elist1">Fixup mode. This specifies the the method used to
save the optional pointer fixup table<blockquote>
<p>0 = none (def)<br>
1 = at beginning of the file<br>
2 = at end (this is probably the most common setting)</p>
</blockquote>
</td>
</tr>
<tr>
<td class="elist2" nowrap>-NOPAD</td>
<td class="elist2">Don't pad end of file to Chunk size. You will
probably want to set this option, otherwise if you set the chunksize above
to some very large size you'll get a gigantic file</td>
</tr>
<tr>
<td class="elist1" nowrap>-TOP <top></td>
<td class="elist1">Top Section (Def. 'START'). By default the linker
will look for a section called [start] for where it should start parsing.
You can change that with this option</td>
</tr>
<tr>
<td class="elist2" nowrap>-WRITEPRE <prefile></td>
<td class="elist2">Write Preload file</td>
</tr>
<tr>
<td class="elist1" nowrap>-WRITEKEY<prekey></td>
<td class="elist1">Write Preload key file</td>
</tr>
<tr>
<td class="elist2" nowrap>-NOSORT</td>
<td class="elist2">Do NOT sort binary files. The point of this is
supposed to be that if not sorted, "file=" files will generally get loaded
in memory consecutively as they are found in the linker file. Of course they
will be no where near the non-file data</td>
</tr>
<tr>
<td class="elist1" nowrap>-READPRE <prefile></td>
<td class="elist1">Read preload file</td>
</tr>
<tr>
<td class="elist2" nowrap>-INCLUDE <incpath></td>
<td class="elist2">Add an include pat</td>
</tr>
</table>
<p><font size="2"><a name="relevant"></a>(*) Never relevant is an over
statement. Of course if you have an include in the middle of a section
then it does matter but there is no reason to do that. Use the insert=
statement instead.</font></p>
<h2><a name="macros"></a>Macro Language</h2>
<p>There is a small macro language built in. It works on a line by line
basis. In other words, macros can NOT span more than one line.
Variables and macros are referenced with %. Example</p>
<blockquote>
<pre>#define file myfile
#define extension .bin
load=%file%%extension%</pre>
</blockquote>
<p>would be the same as</p>
<blockquote>
<pre>load=myfile.bin</pre>
</blockquote>
<p>Note: The format is %blah%. Notice the ending %. This is so you
can put two variables directly next to each other like in the example above.
It is optional only when the next thing following is not a macro/variable.
This has the following consequence. If you follow a macro with a variable
or another macro be sure to remember the trailing %. For example</p>
<blockquote>
<pre>%fadd(2,3)%fadd(4,5)</pre>
</blockquote>
<p>That is most likely a bug. It reads as %fadd(2,3)% followed by
fadd(4,5). The second fadd is not preceded by a % and is therefore not a
macro call. The correct way is</p>
<blockquote>
<pre>%fadd(2,3)%%fadd(4,5)</pre>
</blockquote>
<h3>Macro commands</h3>
<table border="0" cellpadding="5" cellspacing="0" style="border-collapse: collapse">
<tr><td class="elist1">%fnop (...)</td><td class="elist1">anything
inside the parentheses is ignored. Kind of a way to comment something
out.</td></tr>
<tr><td class="elist2">%fset (var,value)</td><td class="elist2">set
the variable called <b>var</b> to <b>value</b></td></tr>
<tr><td class="elist1">%fadd (value1,value2,...)</td><td class="elist1">add
<b>value1</b> to <b>value2</b> (then add <b>value3</b>, ... etc)</td></tr>
<tr><td class="elist2">%fsubtract,%fsub (value1,value2,...)</td><td class="elist2">sub
<b>value2</b> from <b>value1</b> (then subtract <b>value3</b>, ...etc)</td></tr>
<tr><td class="elist1">%fdivide,%fdiv (value1,value2,...)</td><td class="elist1">divide
<b>value1</b> by <b>value2</b> (then divide by <b>value3,</b> ...etc)</td></tr>
<tr><td class="elist2">%fmultiply,%fmul,%fmult (value1,value2,...)</td><td class="elist2">multiply
<b>value1</b> by <b>value2</b> (then multiply by <b>value3</b>, ..etc)</td></tr>
<tr><td class="elist1">%fmodulo,%fmod (value1,value2,...)</td><td class="elist1">return
the remainder when dividing <b>value1</b> by <b>value2</b></td></tr>
<tr><td class="elist2">%fconcatenate,%fconcate,%fcat (str1,
str2, ...)</td><td class="elist2">concatenate <b>str2</b>
on the end of <b>str1</b> (then <b>str3</b> on the end of that etc)</td></tr>
<tr><td class="elist1">%fsubstr (str,index[,len])</td><td class="elist1">return
the substring of <b>str</b>. If just <b>index</b> is specified then
you get the substring from index to the end of the string. If <b>len</b>
is specified then you get <b>len</b> characters after <b>index</b></td></tr>
<tr><td class="elist2">%fleft(str,count)</td><td class="elist2">return
the left <b>count</b> characters of <b>str</b></td></tr>
<tr><td class="elist1">%fright(str,count)</td><td class="elist1">return
the right <b>count</b> characters of <b>str</b></td></tr>
<tr><td class="elist2">%fstrlen (str1,...)</td><td class="elist2">return
the length the <b>str1</b> (plus <b>str2</b>, etc...)</td></tr>
<tr><td class="elist1">%fif (value,truecase[,falsecase])</td><td class="elist1">if
<b>value</b> is true return <b>truecase</b>, else, if there is a <b>falsecase</b>
return it</td></tr>
<tr><td class="elist2">%fequal,%feq (value1,value2)</td><td class="elist2">return
1 if <b>value1</b> is equal to <b>value2</b> (integer compare)</td></tr>
<tr><td class="elist1">%fgreaterthan,%fgt (value,value)</td><td class="elist1">return
1 if <b>value1</b> is greater than to <b>value2</b> (integer compare)</td></tr>
<tr><td class="elist2">%fgreaterthanequal,%fge (value,value)</td><td class="elist2">return
1 if <b>value1</b> is greater than or equal to <b>value2</b> (integer
compare)</td></tr>
<tr><td class="elist1">%flessthan,%flt (value,value)</td><td class="elist1">return
1 if <b>value1</b> is less than to <b>value2</b> (integer compare)</td></tr>
<tr><td class="elist2">%flessthanequal,%fle (value,value)</td><td class="elist2">return
1 if <b>value1</b> is less than or equal to <b>value2</b> (integer compare)</td></tr>
<tr><td class="elist1">%fnotequal,%fne, (value,value)</td><td class="elist1">return
1 if <b>value1</b> is not equal to <b>value2</b> (integer compare)</td></tr>
<tr><td class="elist2">%for (value,value,...)</td><td class="elist2">return
1 if <b>value1</b> is OR <b>value2</b> is not zero (or <b>value3</b>,...
etc) (integer compare)</td></tr>
<tr><td class="elist1">%fand (value,value,...)</td><td class="elist1">return
1 if <b>value1</b> is AND <b>value2</b> is not zero (and <b>value3</b>,...
etc) (integer compare)</td></tr>
<tr><td class="elist2">%fnot(value)</td><td class="elist2">return
1 if <b>value</b> is zero, else return <b>0</b></td></tr>
<tr><td class="elist1">%frand(range) ... (min,max)</td><td class="elist1">return
a random number. If one argument is specified the returned value will
be from 0 to 1 - that number. If two numbers are specified the number
returned will be from min to max-1</td></tr>
<tr><td class="elist2">%fstrcmp(str1,str2)</td><td class="elist2">compare
<b>str1</b> to <b>str2</b>. Return -1 if <b>str1</b> is asciibetically
lower than <b>str2</b>, 0 if they are equal, 1 if <b>str1</b> is
asciibetically higher than <b>str2</b></td></tr>
<tr><td class="elist1">%fstricmp(value,value)</td><td class="elist1">compare
<b>str1</b> to <b>str2</b> case insensitive (using stricmp). Return -1
if <b>str1</b> is asciibetically lower than <b>str2</b>, 0 if they are
equal, 1 if <b>str1</b> is asciibetically higher than <b>str2</b></td></tr>
<tr><td class="elist2">%fascii(str)</td><td class="elist2">return
the ascii value of the first character of <b>str</b></td></tr>
<tr><td class="elist1">%fquote(str)</td><td class="elist1"><span lang="ja">quote
something, don't interpret it</span></td></tr>
<tr><td class="elist2">%fsearch(string1,string2)</td><td class="elist2">find
string2 in string1, if found return the index to the first character
found or -1 if not found</td></tr>
<tr><td class="elist1">%fint(value)</td><td class="elist1">convert
an float to an int. 27.9 becomes 27</td></tr>
<tr><td class="elist2">%fexists(filename)</td><td class="elist2">
returns 1 if the file exists, 0 if it does not</td></tr>
<tr><td class="elist1">%ffilelength(filename)</td><td class="elist1">
returns the length of a file in bytes</td></tr>
<tr><td class="elist2">%fformat(string,arg)</td><td class="elist2">format arg
using string like printf from C/C++
<p>fformat only does one value at a time. The first argument is the
formatting specification. The second is the value to format. A formatting
specification starts with % although it will get addedt of you forget. It
ends with one of the <a href="http://msdn.microsoft.com/library/default.asp?url=/library/en-us/vclib/html/_crt_printf_type_field_characters.asp">these
letters</a></p>
<blockquote>
<div class="tablediv">
<table class="dtTABLE" cellSpacing="1" cellpadding="3" bgcolor="#000000">
<tbody>
<tr>
<th width="16%" class="elist1"><font size="2">Character</font></th>
<th width="21%" class="elist1"><font size="2">Type</font></th>
<th width="63%" class="elist1"><font size="2">Output format</font></th>
</tr>
<tr>
<td width="16%" class="elist1"><b><font size="2">c</font></b></td>
<td width="21%" class="elist1"><b><font size="2">integer</font></b></td>
<td width="63%" class="elist1"><font size="2">specifies a single-byte character</font></td>
</tr>
<tr>
<td width="16%" class="elist1"><b><font size="2">C</font></b></td>
<td width="21%" class="elist1"><b><font size="2">integer</font></b></td>
<td width="63%" class="elist1"><font size="2">specifies a single-byte character.</font></td>
</tr>
<tr>
<td width="16%" class="elist1"><b><font size="2">d</font></b></td>
<td width="21%" class="elist1"><b><font size="2">integer</font></b></td>
<td width="63%" class="elist1"><font size="2">Signed decimal integer.</font></td>
</tr>
<tr>
<td width="16%" class="elist1"><b><font size="2">i</font></b></td>
<td width="21%" class="elist1"><b><font size="2">integer</font></b></td>
<td width="63%" class="elist1"><font size="2">Signed decimal integer.</font></td>
</tr>
<tr>
<td width="16%" class="elist1"><b><font size="2">o</font></b></td>
<td width="21%" class="elist1"><b><font size="2">integer</font></b></td>
<td width="63%" class="elist1"><font size="2">Unsigned octal integer.</font></td>
</tr>
<tr>
<td width="16%" class="elist1"><b><font size="2">u</font></b></td>
<td width="21%" class="elist1"><b><font size="2">integer</font></b></td>
<td width="63%" class="elist1"><font size="2">Unsigned decimal integer.</font></td>
</tr>
<tr>
<td width="16%" class="elist1"><b><font size="2">x</font></b></td>
<td width="21%" class="elist1"><b><font size="2">integer</font></b></td>
<td width="63%" class="elist1"><font size="2">Unsigned hexadecimal integer, using "abcdef."</font></td>
</tr>
<tr>
<td width="16%" class="elist1"><b><font size="2">X</font></b></td>
<td width="21%" class="elist1"><b><font size="2">integer</font></b></td>
<td width="63%" class="elist1"><font size="2">Unsigned hexadecimal integer, using "ABCDEF."</font></td>
</tr>
<tr>
<td width="16%" class="elist1"><b><font size="2">e</font></b></td>
<td width="21%" class="elist1"><b><font size="2">float</font></b></td>
<td width="63%" class="elist1"><font size="2">Signed value having the form [ E]<i>d</i><b>.</b><i>dddd</i>
<b>e</b> [<i>sign</i>]<i>ddd</i> where <i>d</i> is a single
decimal digit, <i>dddd</i> is one or more decimal digits, <i>ddd</i>
is exactly three decimal digits, and <i>sign</i> is + or E</font></td>
</tr>
<tr>
<td width="16%" class="elist1"><b><font size="2">E</font></b></td>
<td width="21%" class="elist1"><b><font size="2">float</font></b></td>
<td width="63%" class="elist1"><font size="2">Identical to the <b>e</b> format except that <b>E</b>
rather than <b>e</b> introduces the exponent.</font></td>
</tr>
<tr>
<td width="16%" class="elist1"><b><font size="2">f</font></b></td>
<td width="21%" class="elist1"><b><font size="2">float</font></b></td>
<td width="63%" class="elist1"><font size="2">Signed value having the form [ E]<i>dddd</i><b>.</b><i>dddd</i>,
where <i>dddd</i> is one or more decimal digits. The number of
digits before the decimal point depends on the magnitude of the
number, and the number of digits after the decimal point depends
on the requested precision.</font></td>
</tr>
<tr>
<td width="16%" class="elist1"><b><font size="2">g</font></b></td>
<td width="21%" class="elist1"><b><font size="2">float</font></b></td>
<td width="63%" class="elist1"><font size="2">Signed value
printed in <b>f</b> or <b>e</b> format, whichever is more
compact for the given value and precision. The <b>e</b>
format is used only when the exponent of the value is less than
E or greater than or equal to the precision argument. Trailing
zeros are truncated, and the decimal point appears only if one or
more digits follow it.</font></td>
</tr>
<tr>
<td width="16%" class="elist1"><b><font size="2">G</font></b></td>
<td width="21%" class="elist1"><b><font size="2">float</font></b></td>
<td width="63%" class="elist1"><font size="2">Identical to the <b>g</b>
format, except that <b>E</b>, rather than <b>e</b>, introduces the
exponent (where appropriate).</font></td>
</tr>
<tr>
<td width="16%" class="elist1"><b><font size="2">s</font></b></td>
<td width="21%" class="elist1"><font size="2">String</font></td>
<td width="63%" class="elist1"><font size="2">specifies a string;
Characters are printed up to the first null character or until the <i>precision</i>
value is reached.</font></td>
</tr>
</tbody>
</table>
</div>
</blockquote>
<p> </p>
<p>If
you know C or C++ it is just using printf. If not you can read about how
the formatting works <a href="http://msdn.microsoft.com/library/default.asp?url=/library/en-us/vclib/html/_crt_format_specification_fields_.2d_.printf_and_wprintf_functions.asp">here</a></p>
<p>example:</p>
<p> fformat(%1.1f,27.4699)</p>
<p>the %1.1f above says; Print at least 1 character and print
one and only one after the decimal so in the example above you'd get
"27.5".</p>
<p>%5.3 would print at least five
characters with 3 after the decimal point so "2.54" would become
"2.540".</p>
<p>%09.4f would make
it "0002.5400".</p>
<p> %9.4f would make it " 2.5400"</p>
<p>Note the the last example of " 2.5400" would
not work in HTML because HTML usually converts a bunch of spaces to one
space. Only inside a <pre> </pre> section are spaces not
ignored. (pre = preformatted). If you need things to be right aligned
use HTML's align property instead of spaces.</td></tr>
</table>
<h2><a name="tips"></a>Tips / Suggestions</h2>
<ul>
<li>Given the above example I generally put a version number inside the top
structure of the file</li>
</ul>
<blockquote>
<blockquote>
<pre>// level.h
struct Level
{
int versionNumber;
int numInstances;
Instance* pInstances;
};</pre>
<pre>// linker file
[start]
long=1 ; version number
long=4 ; number of models
pntr=modeltable</pre>
</blockquote>
<p>This way, anytime I change the format of the data I change the version
number. The loader in the game can check the version number and print an
error message. That way I won't spend hours debugging something just
because I'm using old data with a new version of the code or visa versa.</p>
</blockquote>
<ul>
<li>Since the #define format is the same a C/C++ you can use the same header
file for both the data linker and your code. For example</li>
</ul>
<blockquote>
<blockquote>
<pre>// objectdefs.h
//
#define OBJECTID_PLAYER 1
#define OBJECTID_ZOMBIE 2
#define OBJECTID_CHEST 3</pre>
<pre>// linker file
//
[object_instance_array]
long=%OBJECTID_PLAYER% ; object type
pntr=instance_data_player
long=%OBJECTID_ZOMBIE%
pntr=instance_data_zombie_1
long=%OBJECTID_ZOMBIE%
pntr=instance_data_zombie_2
long=%OBJECTID_ZOMBIE%
pntr=instance_data_zombie_3
long=%OBJECTID_CHEST%
pntr=instance_data_chest_1
long=%OBJECTID_CHEST%
pntr=instance_data_chest_2</pre>
</blockquote>
</blockquote>
<ul>
<li>You can use the nullok=true options to make it possible to optionally
connect data to some structure before you know if it will exist or
not. For example, assume you have a program the generates a 3D joint
hierarchy from a Maya or 3DSMax file.</li>
</ul>
<blockquote>
<blockquote>
<pre>[chest_bone]
float=0.1 2.3 4.5 ; translation
long=2 ; num children
pntr=chest_children;
pntr=chest_attachments,nullok=true
[chest_children]
pntr=left_forearm_bone
pntr=right_forearm_bone
[left_forearm_bone]
float=0.1 2.3 4.5 ; translation
long=0 ; num children
long=0 ; (null), no children
pntr=left_forearm_attachments,nullok=true
[right_forearm_bone]
float=0.1 2.3 4.5 ; translation
long=2 ; num children
long=0 ; (null), no children
pntr=right_forearm_attachments,nullok=true</pre>